【问题标题】:Distutils: build multiple Python extension modules (written in Swig) that share a methodDistutils:构建多个共享方法的 Python 扩展模块(用 Swig 编写)
【发布时间】:2018-11-28 23:47:41
【问题描述】:

我有四个 C++ 文件:A.h、A.cpp、B.h、B.cpp,而 A.h 包含在 B.cpp 中

啊哈:

#pragma once 
void A();

A.cpp:

#include <iostream>
void A() {
    std::cout << "A" << std::endl;
}

B.h:

#pragma once
void B();

B.cpp:

#include "A.h"
#include <iostream>
void B() {
    A();
    std::cout << "B" << std::endl;
}

现在我写了两个 SWIG 接口文件 A.i 和 B.i

人工智能:

%module A
%{
#include "A.h"
%}
%include "A.h"

B.i:

%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'])

【问题讨论】:

    标签: python c++ swig distutils


    【解决方案1】:

    为了清楚起见,我稍微更改了您的文件。

    a.h

    #pragma once 
    
    
    void funcA();
    

    a.cpp

    #include <iostream>
    
    
    void funcA() {
        std::cout << __FILE__ << " " << __LINE__ <<  " " << __FUNCTION__ <<  std::endl;
    }
    

    a.i

    %module a
    %{
        #include "a.h"
    %}
    %include "a.h"
    

    b.h

    #pragma once
    
    
    void funcB();
    

    b.cpp

    #include "a.h"
    #include <iostream>
    
    
    void funcB() {
        std::cout << __FILE__ << " " << __LINE__ <<  " " << __FUNCTION__ <<  std::endl;
        funcA();
    }
    

    b.i

    %module b
    %{
        #include "b.h"
    %}
    %include "b.h"
    

    setup.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,导入保留)。每一步都会调用下一步:

    1. funcB 来自模块 b (b.py)
    2. funcB 来自模块 _b_b.so,或 _b.cpython-35m-x86_64-linux-gnu.so)
      • 从这里开始的一切都发生在 C(或 C++)中
      • 当前的 funcBb.cpp 中的 不同:它由 swig 及其名称生成是 _wrap_funcB
      • 上一个项目符号也适用于 funcAa.cpp
    3. funcB 来自 b.cpp
    4. funcA 来自 a.cpp

    问题是步骤#4.中的代码不在模块_b中,它会在运行时失败。但事情有点奇怪:当调用 funcB 但在模块 (b -> _b) import 时间(我认为这是因为 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)
    

    为了解决它,要么:

    1. 正如您所指出的,在模块 _b 中包含 funcA(通过在 ext_b 中添加 a.cpp源文件列表)。这样,两个模块将是自包含的(来自 funcAPoV),每个模块都将独立工作,但 funcA 将在他们两个
    2. _b 依赖于_a(毕竟,它们是共享对象)。但这不是 Python 扩展模块的使用方式,它不适用于 Win(以及某些 Nix 风格)。所以,这更像是一个(蹩脚的)解决方法(gainarie
    3. a.cpp 构建到不同的共享库中(.so,但不是 Python 扩展模块)供两个模块使用。不用说,在运行时,它们中的每一个都需要 .so 存在

    显然,选项 #3. 是完美的候选人。但是 distutils ([Python.Docs]: API Reference) 没有提供所需的功能 OOTB (显然构建一个扩展模块和一个它所依赖的外部共享库,这不是一个场景distutils 的目标),或者至少,我找不到任何东西。
    有一个 build_clib 模块,它提供了构建 static 库(供扩展模块使用)的功能,但这与选项 #1..

    setup.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 个方法被覆盖,但实际上只有一小部分发生了变化(cmets 没有从基类方法(Python3.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.exedir 应该在 %PATH%

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多