我使用SWIG将C ++库包装为Python库。 C ++库公开了抽象类,供用户继承它,因此我们使用SWIG中的控制器来处理。它的工作原理很好(有一些调整)。
一个问题是这个C ++类有两个方法如下:
class Base {
void* getObject();
void doSomething(void* o);
}
希望用户实现这些方法,然后将用户在getObject()中返回的对象传递给doSomething()方法。
问题在于,当浏览SWIG时,Python中的doSomething()方法会收到一个包装类型为“void *”的SwigPyObject,因此我们不能像我们希望的那样使用原始的Python对象方法。 而且不是选择,因为它是Python(或者是它?)。
有没有人有任何见解?
我在这里和那里发现了一些相关的问题,但它们似乎都没有解决我的问题,而且我已经尝试了许多方法来解决它,但没有任何成功。
如果您需要更多详细信息,请告诉我,我会提供。
非常感谢!
答案 0 :(得分:0)
首先,我们希望将您的代码变为真实可运行的东西。我根据你所展示的小代码编写了我自己的test.hh,让我们在某种程度上运用这个设计:
class Base {
public:
void runMe() {
std::cerr << "Getting object\n";
void *result = getObject();
std::cerr << "Got: " << result << "\n";
doSomething(result);
std::cerr << "Did a thing\n";
}
virtual ~Base() {}
protected:
virtual void* getObject() = 0;
virtual void doSomething(void* o) = 0;
};
我们最初可以将其包装为:
%module(directors="1") test
%{
#include "test.hh"
%}
%feature("director") Base;
%include "test.hh"
并制作一个测试用例,以展示我们如何在Python中使用它:
import test
class Foobar(test.Base):
def getObject(self):
return [1,2,3]
def doSomething(self, thing):
print(thing)
f=Foobar()
f.runMe()
但是这个阶段还没有成功,因为我们没有告诉SWIG如何在Python中有意义地处理void*
。
这里的一般想法是我们想让void*
在接口内用作PyObject*
。我们可以通过directorin和directorout typemap配对来做到这一点。从广义上讲,我们需要解决两个问题:
void*
我们得到的PyObject*
真的不是getObject()
会怎样?如果我们假设doSomething()
调用和PyObject
调用之间存在1:1映射,那么引用计数相当简单,我们可以在界面中编写两个类型映射保留对void*
的引用,然后在需要时将其从%module(directors="1") test
%{
#include "test.hh"
%}
%feature("director") Base;
%typemap(directorout) void *getObject %{
Py_INCREF($1);
$result = $1;
%}
%typemap(directorin) void *o %{
$input = static_cast<PyObject*>($1);
// Director call will decref when we're done here - it assumes ownership semantics, not borrowed
%}
%include "test.hh"
转回(请注意,我们还通过添加1:1限制完全回避了问题#2)。
因此,使用这两个类型图,我们的界面变为:
swig -Wall -python -py3 -c++ test.i
g++ -Wall -Wextra -shared -o _test.so -I/usr/include/python3.5 test_wrap.cxx -std=c++11 -fPIC
python3 run.py
Getting object
Got: 0x7fce97b91c48
[1, 2, 3]
Did a thing
当我们这样测试时:
runMe
但是,如果我们将此处的sematics更改为不完全是1:1,那么我们就会遇到问题,例如make void runMe() {
std::cerr << "Getting object\n";
void *result = getObject();
std::cerr << "Got: " << result << "\n";
doSomething(result);
std::cerr << "Second time\n";
doSomething(result);
std::cerr << "Did a thing\n";
}
就是这样:
doSomething
现在哪个段错误,因为在第一次调用Py_INCREF
之后引用已递减。
在这个阶段,显而易见的事情是在directorin类型地图中添加对getObject()
的调用,但这不完全是故事 - 我们永远不会 call现在发布runMe()
的结果,它只是在Base
结尾处超出范围。
我倾向于解决的方法是在virtual void cleanupThing(void* o) {} // Default nothing, not mandatory
界面添加另一个电话:
%rename
有了这个,我们可以让你的SWIG接口实现(并隐藏,如果我们想要)完全在Python控制器内部调用。这样做的方法是使用一些%ignore
和runMe
以及一些宏观技巧:
因此,通过以下对SWIG界面的调整,我们现在可以正确地使用%module(directors="1") test
%{
#include "test.hh"
%}
%feature("director") PyBase;
%typemap(directorout) void *getObject %{
Py_INCREF($1);
$result = $1;
%}
%typemap(directorin) void *o %{
$input = static_cast<PyObject*>($1);
Py_INCREF($input); // Not borrowed now
// Director call will decref when we're done here
%}
// Python won't even know cleanupThing existed because we use it internally in the Python binding
%ignore PyBase::cleanupThing;
%feature("nodirector") PyBase::cleanupThing;
// This is a sleight of hand trick with SWIG so we can add another type into the hierarchy without anyone really noticing
%rename(Base) PyBase;
%{
class PyBase : public Base {
void cleanupThing(void *o) {
Py_DECREF(o);
}
};
%}
#define Base PyBase
%include "test.hh"
的第二个版本:
cleanupThing
从runMe
打电话给void runMe() {
std::cerr << "Getting object\n";
void *result = getObject();
std::cerr << "Got: " << result << "\n";
doSomething(result);
std::cerr << "Second time\n";
doSomething(result);
std::cerr << "Did a thing\n";
cleanupThing(result);
}
:
Getting object
Got: 0x7ff65dccfd08
[1, 2, 3]
Second time
[1, 2, 3]
Did a thing
现在运行时确实给出:
checked
(特别是如果语义比简单地来回传递到同一实例的局部变量更复杂,则存在其他可能的解决方案。)