python - Distutils:构建多个共享方法的 Python 扩展模块(用 Swig 编写)
问题描述
我有四个 C++ 文件:Ah、A.cpp、Bh、B.cpp,并且 Ah 包含在 B.cpp 中
啊:
#pragma once
void A();
A.cpp:
#include <iostream>
void A() {
std::cout << "A" << std::endl;
}
乙:
#pragma once
void B();
B.cpp:
#include "A.h"
#include <iostream>
void B() {
A();
std::cout << "B" << std::endl;
}
现在我写了两个 SWIG 接口文件 Ai 和 Bi
艾:
%module A
%{
#include "A.h"
%}
%include "A.h"
双:
%module B
%{
#include "B.h"
%}
%include "B.h"
setup.py 文件是:
from distutils.core import setup, Extension
A_ext = Extension( "_A", [ "A.i", "A.cpp", ], swig_opts = ['-c++'], extra_compile_args = ['-g'])
B_ext = Extension( "_B", [ "B.i", "B.cpp", ], swig_opts = ['-c++'], extra_compile_args = ['-g'])
setup(
name = "test",
version = "1.0",
ext_modules = [ A_ext, B_ext ],
py_modules = [ "A", "B" ]
)
如果我键入下面的命令,它将显示“A”。
python -c 'import A; A.A()'
如果我键入下面的命令,则会出现分段错误:
python -c 'import B; B.B()'
我该怎么做才能让这个命令正确运行?既然不想多次编译B.cpp,那除了下面的还有什么办法吗?
B_ext = Extension( "_B", [ "B.i", "A.cpp", "B.cpp", ], swig_opts = ['-c++'], extra_compile_args = ['-g'])
解决方案
为了清楚起见,我稍微更改了您的文件。
啊:
#pragma once
void funcA();
a.cpp:
#include <iostream>
void funcA() {
std::cout << __FILE__ << " " << __LINE__ << " " << __FUNCTION__ << std::endl;
}
艾:
%module a
%{
#include "a.h"
%}
%include "a.h"
: _
#pragma once
void funcB();
b.cpp:
#include "a.h"
#include <iostream>
void funcB() {
std::cout << __FILE__ << " " << __LINE__ << " " << __FUNCTION__ << std::endl;
funcA();
}
双:
%module b
%{
#include "b.h"
%}
%include "b.h"
设置.py:
from distutils.core import setup
from distutils.extension import Extension
a = "a"
b = "b"
ext_a = Extension("_" + a, [a + ".i", a + ".cpp"], swig_opts=("-c++",), extra_compile_args=["-g"])
ext_b = Extension("_" + b, [b + ".i", b + ".cpp"], swig_opts=("-c++",), extra_compile_args=["-g"])
setup(
name="test",
version="1.0",
ext_modules=[ext_a, ext_b],
py_modules=[a, b]
)
调用b.funcB时会发生什么(简化)(只有stacktrace,导入留在一边)。每个步骤都会调用下一个:
- 来自模块b ( b.py )的funcB
- 来自模块_b ( _b.so或_b.cpython-35m-x86_64-linux-gnu.so )
的funcB
- 从这里开始的一切都发生在C ( 或C++ )
- 当前funcB与b.cpp中的不同:它由swig生成,名称为_wrap_funcB
- 上一个项目符号也适用于funcA和a.cpp
- 来自b.cpp的funcB
- 来自a.cpp的funcA
问题是步骤#4中的代码。不在模块_b中,它会在运行时失败。但事情有点奇怪:调用funcB时没有出现故障(核心转储),但在模块(b -> _b)导入时间(我认为这是因为swig的幕后魔术而发生的),如见下文。
输出:
[cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q050938128]> ~/sopr.sh *** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages *** [064bit prompt]> ls a.cpp a.h a.i b.cpp b.h b.i setup.py [064bit prompt]> python3 setup.py build > /dev/null 2>&1 [064bit prompt]> echo $? 0 [064bit prompt]> ls a.cpp a.h a.i a.py a_wrap.cpp b.cpp b.h b.i b.py build b_wrap.cpp setup.py [064bit prompt]> ls ./build/lib.linux-x86_64-3.5 _a.cpython-35m-x86_64-linux-gnu.so _b.cpython-35m-x86_64-linux-gnu.so [064bit prompt]> PYTHONPATH=${PYTHONPATH}:./build/lib.linux-x86_64-3.5 python3 -c "import _a;_a.funcA()" a.cpp 6 funcA [064bit prompt]> PYTHONPATH=${PYTHONPATH}:./build/lib.linux-x86_64-3.5 python3 -c "import _b" Segmentation fault (core dumped)
为了解决它,要么:
- 正如您所指出的,在模块_b中包含funcA(通过在ext_b的源文件列表中添加a.cpp )。这样,两个模块都将是自包含的(来自funcA的PoV),每个模块都将独立工作,但funcA也将在它们两个中重复
- 使_b依赖于_a(毕竟,它们是共享对象)。但这不是Python扩展模块的使用方式,它不适用于Win(以及某些Nix风格)。所以,这更像是一个(蹩脚的)解决方法(gainarie)
- 将 a.cpp构建到不同的共享库(一个.so,但不是Python扩展模块)以供两个模块使用。不用说,在运行时,它们中的每一个都需要.so存在
显然,对于选项#3。是完美的候选人。但是distutils ( [Python.Docs]: API Reference ) 没有提供所需的OOTB功能(显然构建一个扩展模块和一个它所依赖的外部共享库,这不是distutils的目标),或者至少,我找不到任何东西。
有一个build_clib模块,它提供构建静态库的功能(由扩展模块使用),但这与选项#1 相同。.
设置.py:
import sys
import os
from distutils.core import setup
from distutils.extension import Extension
from distutils.command.build_clib import build_clib
from distutils.command.build_ext import build_ext
from distutils.ccompiler import CCompiler
__win = sys.platform[:3].lower() == "win"
export_symbols_option = "export_symbols"
class build_clib_dyn(build_clib):
def finalize_options(self):
self.set_undefined_options('build',
('build_lib', 'build_clib'),
('build_temp', 'build_temp'),
('compiler', 'compiler'),
('debug', 'debug'),
('force', 'force'))
self.libraries = self.distribution.libraries
if self.libraries:
self.check_library_list(self.libraries)
if self.include_dirs is None:
self.include_dirs = self.distribution.include_dirs or []
if isinstance(self.include_dirs, str):
self.include_dirs = self.include_dirs.split(os.pathsep)
def build_libraries(self, libraries):
for (lib_name, build_info) in libraries:
sources = build_info.get('sources')
if sources is None or not isinstance(sources, (list, tuple)):
raise DistutilsSetupError(
"in 'libraries' option (library '%s'), "
"'sources' must be present and must be "
"a list of source filenames" % lib_name)
sources = list(sources)
macros = build_info.get('macros')
include_dirs = build_info.get('include_dirs')
objects = self.compiler.compile(sources,
output_dir=self.build_temp,
macros=macros,
include_dirs=include_dirs,
debug=self.debug)
self.compiler.link(CCompiler.SHARED_OBJECT, objects, self.compiler.library_filename(lib_name, lib_type="shared"),
output_dir=self.build_clib,
export_symbols=build_info.get(export_symbols_option),
debug=self.debug)
if __win:
class build_ext_w_dyn_dep(build_ext):
def finalize_options(self):
super(build_ext_w_dyn_dep, self).finalize_options()
self.library_dirs.append(os.path.dirname(self.build_temp))
else:
class build_ext_w_dyn_dep(build_ext):
pass
a_name = "a"
b_name = "b"
common_name = a_name + b_name + "common"
swig_opts = ["-c++"]
libraries = [common_name]
lib_common_build_info = {"sources": [a_name + ".cpp"]}
if __win:
extra_compile_args = None
extra_link_args = None
lib_common_build_info[export_symbols_option] = ["funcA"]
else:
extra_compile_args = ["-g"]
extra_link_args = ["-Wl,-rpath,${ORIGIN}"]
lib_common_info = (common_name, lib_common_build_info)
ext_a = Extension("_" + a_name, [a_name + ".i"], libraries=libraries, extra_compile_args=extra_compile_args, extra_link_args=extra_link_args, swig_opts=swig_opts)
ext_b = Extension("_" + b_name, [b_name + ".i", b_name + ".cpp"], libraries=libraries, extra_compile_args=extra_compile_args, extra_link_args=extra_link_args, swig_opts=swig_opts)
setup(
name="test",
version="1.0",
libraries=[lib_common_info],
cmdclass={"build_clib": build_clib_dyn, "build_ext": build_ext_w_dyn_dep},
ext_modules=[ext_a, ext_b],
py_modules=[a_name, b_name]
)
备注:
- build_clib_dyn扩展了 build_clib,因为它的功能必须修改。2 个方法被覆盖,但实际上只有一小部分发生了变化(注释没有从基类方法(Python 3.5.4代码库)复制,以减少代码量,但这并不算真正的变化)
- 该任务花费了相当多的distutils代码浏览,因为一些选项没有记录(而且我不是很熟悉)
- 还需要一些Nix知识,因为在加载不在系统路径中的共享库时事情会变得棘手,并且还要保持事情顺利(例如,不改变${LD_LIBRARY_PATH})
- build_ext_w_dyn_dep类似于build_clib_dyn(仅限Win)。由于在构建Win动态链接库(.dll)时,会生成 2 个文件,并且在这种情况下它们不在同一个目录中,因此需要对库搜索路径进行一些调整
- Python 3 和 Python 2兼容
输出(再次运行上述命令):
[064bit prompt]> ls a.cpp a.h a.i b.cpp b.h b.i setup.py [064bit prompt]> python3 setup.py build > /dev/null 2>&1 [064bit prompt]> echo $? 0 [064bit prompt]> ls a.cpp a.h a.i a.py a_wrap.cpp b.cpp b.h b.i b.py build b_wrap.cpp setup.py [064bit prompt]> ls build/lib.linux-x86_64-3.5/ _a.cpython-35m-x86_64-linux-gnu.so _b.cpython-35m-x86_64-linux-gnu.so libabcommon.so [064bit prompt]> ldd build/lib.linux-x86_64-3.5/_a.cpython-35m-x86_64-linux-gnu.so linux-vdso.so.1 => (0x00007fffadb49000) libabcommon.so => /home/cfati/Work/Dev/StackOverflow/q050938128/build/lib.linux-x86_64-3.5/libabcommon.so (0x00007f91cd50f000) libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f91cd18d000) libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f91ccf77000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f91ccbad000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f91cc990000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f91cc687000) /lib64/ld-linux-x86-64.so.2 (0x00007f91cd916000) [064bit prompt]> PYTHONPATH=${PYTHONPATH}:./build/lib.linux-x86_64-3.5 python3 -c "import _a;_a.funcA()" a.cpp 6 funcA [064bit prompt]> PYTHONPATH=${PYTHONPATH}:./build/lib.linux-x86_64-3.5 python3 -c "import _b;_b.funcB()" b.cpp 7 funcB a.cpp 6 funcA
- 增加了对Win的支持(我猜,这对问题不是很重要)
- 由于模块结构很简单,因此可以在setup.py级别完成所有操作(不必修改源文件)
- 唯一的额外要求是swig.exe的目录应该在%PATH%
推荐阅读
- node.js - 使用 node-usb this.open() , LIBUSB_ERROR_ACCESS
- sql - 调用 SQL 命令在 Power shell 核心 6.0.2 中不起作用
- cmake - 如何在 cmake 中编译具有不同 2 标志的源文件?
- java - 为什么函数必须需要返回两个布尔值才能避免错误?
- javascript - 使轮播无限或重复
- symfony - Wamp 3.0.6 服务器无法运行 Composer 和 Symfony
- asp.net-mvc-4 - 发布后选择下拉值
- angular - 角度元素如何订阅主机发出的事件?
- c# - 停靠背景图像 c#
- java - 获取Java中用户定义类的闭区间子集