使用 SWIG 包装调用另一个对象成员函数的 C++ 类

Posted

技术标签:

【中文标题】使用 SWIG 包装调用另一个对象成员函数的 C++ 类【英文标题】:Using SWIG to wrap a C++ class that calls another objects member function 【发布时间】:2014-01-18 01:33:35 【问题描述】:

我正在使用 swig 为 c++ 类编写一个包装器,以便与 python 一起使用。

当我尝试执行 from CSMPy import *CSMPy 是我的模块)时,我收到以下消息:

ImportError: dlopen(/Users/MUL_mac2/anaconda/lib/python2.7/site-packages/_CSMPy.so, 2): Symbol not found: __ZN4csmp4VSetILm2EE6ResizeERKSt5dequeIiSaIiEERKS2_ImSaImEESA_m
  Referenced from: /Users/MUL_mac2/anaconda/lib/python2.7/site-packages/_CSMPy.so
  Expected in: dynamic lookup

一点背景:

我有一个接口文件,它包含一个包含我的包装类的头文件:

这个类有一个对象作为私有成员。

然后我想将一些std::deque<int> 类型的对象传递给一个成员函数 这个对象像这样:this->object.Function(int_deque_a,int_deque_b) 其中object 是我使用 swig 包装的类的成员。

当我评论以上行时,一切都像魅力一样。 我传递的所有容器都是有效的数据类型,可传递给此对象成员函数并包含正确数量的条目。

一切都会编译,这只发生在导入模块时。

我在这里错过了什么?

我正在使用 distutils 使用 python setup.py install 进行编译

setup.py:

CSMPy_module = Extension('_CSMPy',
                                  include_dirs = [Bunch of include directories here],
                                  library_dirs = ['MyLibraryPath'],
                                  libraries = ['MyLibrary'],
                                  sources=['CSMPy_wrap.cxx', 'WrapperClass.cpp'],
                                  )
setup (name = 'CSMPy',
   version = '0.1',
   author = "My name",
   description = """Simple Test""",
   ext_modules = [CSMPy_module],
   py_modules = ["CSMPy"],
   )

MyLibrary 是一个静态库。

编辑 1: 我正在为您提供一个可以向所有人展示的代码版本

设置.h

#include <iostream>
#include <vector>
#include <deque>

#include "VSet.h"


class Setup 
public:
  Setup();
  ~Setup();

  void InitializeSetup();

private:

  std::deque<size_t> npes;
  std::deque<size_t> epes;

  std::deque<std::vector<size_t> > eni; //plist
  std::deque<std::vector<csmp::int32> > enb; //pfverts
  std::deque<std::vector<csmp::double64> > ncl; //pelmt
  std::map<size_t, csmp::int32> bnf; //bflags

  std::deque<csmp::int32>   et;
  csmp::VSet<2U> v;
;

安装程序.cpp

#include "Setup.h"


Setup::Setup() 

  std::cout<<"Setup initialized."<<std::endl;



Setup::~Setup() 



void Setup::InitializeSetup() 


  for(size_t i = 0; i < this->eni.size(); i++) 

      this->npes.push_back(this->eni[i].size());
  

  for(size_t i = 0; i < this->enb.size(); i++) 

    this->epes.push_back(this->enb[i].size());

  
  this->v.Resize(this->et, npes, epes, this->ncl.size()); //This is the line that does not work

CSMPy.i

%module CSMPy

%
#define SWIG_FILE_WITH_INIT
#include "stdlib.h"
#include <vector>
#include <deque>
#include <map>
#include "VSet.cpp"
#include "Setup.h"
#include "Number_Types.h"
%

%include "Number_Types.h"

%include "std_map.i"
%include "std_vector.i"
%include "std_deque.i"

// Instantiate templates used by CSMPy
namespace std 
  %template() pair<size_t, csmp::int32>;
  %template() pair<size_t, csmp::double64>;

  %template() pair<size_t, vector<size_t> >;
  %template() pair<size_t, vector<csmp::int32> >;
  %template() pair<size_t, vector<csmp::double64> >;

  %template(Deque_SizeT) deque<size_t>;
  %template(Deque_Int) deque<csmp::int32>;

  %template(Vector_SizeT) vector<size_t>;
  %template(Vector_Int32) vector<csmp::int32>;
  %template(Vector_Double64) vector<csmp::double64>;

  %template(Deque_Double64) deque<csmp::double64>;

  %template(Deque_Vector_Int) deque<vector<csmp::int32> >;
  %template(Deque_Vector_SizeT) deque<vector<size_t> >;
  %template(Deque_Vector_Double64) deque<vector<csmp::double64> >;

  %template(Map_SizeT_Int) map< size_t, csmp::int32>;
  %template(Map_SizeT_Double64) map< size_t, csmp::double64>;

  %template(Map_SizeT_Vector_SizeT) map< size_t, vector<size_t> >;
  %template(Map_SizeT_Vector_Int) map< size_t, vector<csmp::int32> >;
  %template(Map_SizeT_Vector_Double64) map< size_t, vector<csmp::double64> >;

%include "Setup.h"

编辑 2:

我做了 nm -gC myLib.so

我发现了这个回声

__ZN4csmp4VSetILm2EE6ResizeERKNSt3__15dequeIiNS2_9allocatorIiEEEERKNS3_ImNS4_ImEEEESC_m

c++ 倾斜告诉我:

csmp::VSet<2ul>::Resize(std::__1::deque<int, std::__1::allocator<int> > const&, std::__1::deque<unsigned long, std::__1::allocator<unsigned long> > const&, std::__1::deque<unsigned long, std::__1::allocator<unsigned long> > const&, unsigned long)

对此有几点说明,我已切换到使用 clang++ 作为我的编译器并手动编译。我还在我的 .i 文件中添加了#include "VSet.cpp"。 (参见上一篇文章中的编辑)

我现在在 python 中导入时遇到此错误:

Symbol not found: __ZN4csmp5VData6InTextERSt14basic_ifstreamIcSt11char_traitsIcEE
  Referenced from: ./_CSMPy.so
  Expected in: flat namespace

我还创建了一个用于实例化对象的 main,并且对 Initialize() 的调用有效。

【问题讨论】:

背景部分太难看懂,能不能贴一些代码(只是相关部分)? 我不能发布任何代码,因为它是专有的。我已经为你伪化了代码。 这可能与您编译和链接CSMPy 与gcc 的方式有很大关系。请提供用于构建模块的命令行。 我正在使用 distutils 来编译我的代码。如果可能的话,你能给我一个 gcc 命令来编译这个类吗?我正在使用 MacOSX Mavericks。 等等,包括 .cpp 确实改变了什么?现在您收到流链接错误?但是当你从 main.exe 运行它时它工作正常吗? main.exe 版本在更改之前是否也可以正常工作?调整大小是否使用 std::ifstream?如果是这样,如果您将 Resize 的那部分注释掉怎么办?你能解开吗,我现在不容易访问 c++filt。 【参考方案1】:

找不到符号

__ZN4csmp4VSetILm2EE6ResizeERKSt5dequeIiSaIiEERKS2_ImSaImEESA_m

在.so中。感谢 Dave 对它的拆解,我们现在知道它指的是

csmp::VSet<2ul>::Resize(
    const std::deque<int>&, 
    const std::deque<unsigned long> &, 
    const std::deque<unsigned long> &)

因此,根据您发布的内容,您有两种类型的双端队列有点奇怪。

这里有一些事情可以尝试:

    验证您的 _CSMP.so 是否链接到编译器附带的 STL 库,您可能需要在 setup.py 中指定额外的开关或字段。你说,当 Resize 不存在时,你的代码可以工作,所以这不太可能是问题所在。 在 setup.py 中打开详细输出,以便您可以看到编译和链接命令行参数 确保在 SWIG .i 文件中包含 std_deque.i。您没有收到编译错误,因此这不太可能是问题所在。 验证您是否已在 .i 中使用 %template(IntDeque) std::deque&lt;int&gt; 实例化了您的 deque&lt;int&gt;,因为 Python 对 C++ 模板一无所知,并且模板不是类,而是一个配方,因此编译器可以为您创建一个类。如果您确实同时使用 int 和 unsigned long,则必须将两者都实例化。我只在您的代码中看到 int 和 size_t 。您不能假设 size_t 与 unsigned long 相同。 确认您的 DLL 包含此 Resize 方法用于 unsigned int 的实例化。在您的 C++ 代码中。我认为您定义了 size_t 版本,还是 unsigned long 出乎意料?

关于#5:

SWIG 生成一个头文件和一个源文件。在标头中,它放置了遵循 Python C API 的函数并将它们注册到 Python 解释器中,在这些函数的主体中,它确定要从您的库中调用哪些 C/C++ 函数。在 DLL 中找不到上述 Resize 的事实表明 SWIG 认为需要这种 Resize 重载,因此从它生成的函数中调用它,但您的 C++ lib 没有实例化它。

这怎么可能?在您的 C++ 库中,您有一个带有 Resize 方法的类模板。类模板的技巧是编译器只会为 DLL 中使用的方法生成代码(所以如果你的类定义了 5 个方法但你的 DLL 只使用了 1 个,它不会为其他 4 个方法生成代码), except 如果您在库中显式实例化模板。你可以通过声明来做到这一点

template class VSet<2ul>; 

(无论 2ul 代表什么)在您的 C++ DLL 或包装 DLL 中通过 .i 文件中的 %template 指令。这将实例化 VSet&lt;2ul&gt; 的所有方法,因此 Resize 也将在那里。 IF 这样生成的 Resize 具有参数 deque&lt;int&gt;deque&lt;unsigned long&gt;。您的代码表明您假设 size_t 是无符号整数。如果 size_t 的 typedefd 为 unsigned int,则 SWIG 应该能够处理它,但可能存在错误。最好不要假设。您可以为 unsigned int 添加 Resize 重载。或者,您可以在 Setup 中创建一个内联扩展方法,采用两个 unsigneld long deques 并调用 size_t 版本。像

%template DequeULong std::deque<unsigned long>

%extend Setup 
   void Resize(const DequeInt& a, const DequeULong& b) 
   
       DequeSizet c;
       ... copy b into a DequeSizet
       Resize(a, c);
   

【讨论】:

拆解__ZN4csmp4VSetILm2EE6ResizeERKSt5dequeIiSaIiEERKS2_ImSaImEESA_m 产生csmp::VSet&lt;2ul&gt;::Resize(std::deque&lt;int, std::allocator&lt;int&gt; &gt; const&amp;, std::deque&lt;unsigned long, std::allocator&lt;unsigned long&gt; &gt; const&amp;, std::deque&lt;unsigned long, std::allocator&lt;unsigned long&gt; &gt; const&amp;, unsigned long) +1。我给出了一个答案,主要是因为它太长了,不适合作为这个答案的评论。 csmp::VSet::Resize() 方法使用两种类型的双端队列。那是对的。但它是对象 VSet 的函数。我要包装的类包含一个 VSet 类型的对象,它正在调用该对象的 Resize 函数。实际的函数调用形式为: void ResizeVSet(containers) this->v.Resize(containers); //其中 v 是 csmp::VSet 的一个实例 我必须在我的接口文件中声明这个函数吗? deque 在我的接口文件中实例化。是的,我对 int 和 unsigned long 都这样做。我也 %include "std_deque.i" 我的问题还有一条线索:头文件定义函数 resize 如下: void Resize( const std::deque<:int32>& types, const std::deque& n, const std::deque& e, size_t num );我知道 csmp::int32 被 typedef 为 int。我的 python 模块找不到的函数正在寻找 const deque。我怀疑传递一个 deque 不等于 deque 这可能吗? @LukasMosser 是的,size_t 的定义将取决于编译器;也看看我的编辑,你可能会强制实例化 VSet (同样没有看到你的代码,我在黑暗中为 VSet 拍摄)。【参考方案2】:

问题很可能不是编译问题。您的头文件和实现文件之间很可能不匹配。标头承诺了一个您没有实现的接口。如果您从不在 C++ 代码中调用该成员函数,您将不会在独立的、仅限 C++ 的应用程序中看到未定义的引用。

当您告诉 SWIG 包装该 C++ 标头时,标头和实现之间的不匹配成为一个真正的问题。生成的 SWIG 代码包含对该未实现函数的引用。动态链接失败,因为从未定义过该函数。

那么它是什么功能呢?查看报错信息:

Symbol not found: __ZN4csmp4VSetILm2EE6ResizeERKSt5dequeIiSaIiEERKS2_ImSaImEESA_m

这会以一种非常复杂(名称混乱)的方式准确地告诉您缺少什么。复制该符号,打开终端窗口,然后发出命令echo &lt;paste mangled name here&gt; | c++filt

echo __ZN4csmp4VSetILm2EE6ResizeERKSt5dequeIiSaIiEERKS2_ImSaImEESA_m | c++filt

c++filt 实用程序是 Mac 和 Linux 机器上非常有用的功能。在这种情况下,它会为您提供丢失符号的未损坏名称。请参阅我对 Schollii 回答的评论。

【讨论】:

【参考方案3】:

我正在开始一个新的答案,因为 VSet 未包装的事实使我的大多数其他答案与此问题无关(尽管其中的所有内容仍然正确)。 Setup 具有 VSet 类型的数据成员这一事实与 SWIG 无关,因为您没有直接从 Python 访问 Setup::v。

验证 Setup 是否在没有 Python 或 SWIG 的情况下工作:创建一个 void main(),在其中实例化一个 Setup 并调用其 InitializeSetup() 方法,构建并运行。由于找不到符号,您很可能会遇到相同的运行时错误。

Setup 对象代码正在寻找

csmp::VSet<2ul>::Resize(
    const std::deque<int>&, 
    const std::deque<unsigned long> &, 
    const std::deque<unsigned long> &,
    unsigned long)

所以验证你的 DLL 有这个符号:

~> nm -gC yourLib.so

它可能不会。是否有其他实例化的 Resize 重载?这可以提供线索。

编译器无法实例化 VSet 的 Resize 可能有多种原因。例如,模板类的方法定义必须出现在 .h 中,否则编译器如何知道要生成什么代码?如果您告诉编译器编译 VSet.cpp,它不会在 .o 中生成任何内容,除非您显式实例化特定类型的模板。然后 .o 将包含具有该类型的特定模板类的类的目标代码。我喜欢让我的方法定义单独的类定义,但是我将 .cpp 包含在 .h 中,因为 .h 的任何用户还需要包含 .cpp 以便编译器可以生成正确的代码。对你来说,这意味着你将在 VSet.h 的底部有一个 #include "VSet.cpp" // templated code

【讨论】:

老实说,我正在考虑改用 boost.python 而不是 swig,这似乎是一个永无止境的故事。

以上是关于使用 SWIG 包装调用另一个对象成员函数的 C++ 类的主要内容,如果未能解决你的问题,请参考以下文章

没有默认构造函数的对象成员的 Swig setter

swig-php 包装器使用指针,c 代码是一个数组

将外部指针包装到 SWIG 数据结构中

使用 swig 包装类似读/写的函数

swig perl 包装器没有为类成员函数生成

如何使用 SWIG 包装带有可变参数的 C 函数