使用 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<int>
实例化了您的 deque<int>
,因为 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<2ul>
的所有方法,因此 Resize 也将在那里。 IF 这样生成的 Resize 具有参数 deque<int>
和 deque<unsigned long>
。您的代码表明您假设 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<2ul>::Resize(std::deque<int, std::allocator<int> > const&, std::deque<unsigned long, std::allocator<unsigned long> > const&, std::deque<unsigned long, std::allocator<unsigned long> > const&, unsigned long)
。
+1。我给出了一个答案,主要是因为它太长了,不适合作为这个答案的评论。
csmp::VSet::Resize() 方法使用两种类型的双端队列。那是对的。但它是对象 VSet 的函数。我要包装的类包含一个 VSet 类型的对象,它正在调用该对象的 Resize 函数。实际的函数调用形式为: void ResizeVSet(containers) this->v.Resize(containers); //其中 v 是 csmp::VSet 的一个实例 我必须在我的接口文件中声明这个函数吗? deque问题很可能不是编译问题。您的头文件和实现文件之间很可能不匹配。标头承诺了一个您没有实现的接口。如果您从不在 C++ 代码中调用该成员函数,您将不会在独立的、仅限 C++ 的应用程序中看到未定义的引用。
当您告诉 SWIG 包装该 C++ 标头时,标头和实现之间的不匹配成为一个真正的问题。生成的 SWIG 代码包含对该未实现函数的引用。动态链接失败,因为从未定义过该函数。
那么它是什么功能呢?查看报错信息:
Symbol not found: __ZN4csmp4VSetILm2EE6ResizeERKSt5dequeIiSaIiEERKS2_ImSaImEESA_m
这会以一种非常复杂(名称混乱)的方式准确地告诉您缺少什么。复制该符号,打开终端窗口,然后发出命令echo <paste mangled name here> | 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++ 类的主要内容,如果未能解决你的问题,请参考以下文章