如何在 Python 中处理 SWIG 导向器方法中的 void 指针
Posted
技术标签:
【中文标题】如何在 Python 中处理 SWIG 导向器方法中的 void 指针【英文标题】:How to handle void pointers in SWIG director methods in Python 【发布时间】:2018-01-06 23:26:16 【问题描述】:我正在使用 SWIG 将 C++ 库包装为 Python 库。 C++ 库公开了抽象类供用户继承,因此我们使用 SWIG 中的导向器来处理。它工作正常(稍作调整)。
一个问题是这个 C++ 类有两个方法如下:
class Base
void* getObject();
void doSomething(void* o);
用户应该实现这些方法,然后用户在 getObject() 中返回的对象被传递给 doSomething() 方法。
问题在于,当通过 SWIG 时,Python 中的 doSomething() 方法接收到一个包装类型为“void*”的 SwigPyObject,因此我们不能像希望的那样使用原始 Python 对象方法。 并且强制转换不是一种选择,因为它是 Python(或者是它?)。
有人有什么见解吗?
我在这里和那里发现了一些相关的问题,但似乎没有一个能完全解决我的情况,而且我已经尝试了很多方法来解决它,但没有任何成功。
如果您需要更多详细信息,请告诉我,我会提供。
非常感谢!
【问题讨论】:
我不明白你在这里的设计 - 为什么是 void* 而不是类型层次结构。在实际使用中,您实际使用哪些类型? 我们提供的是一个库,由用户定义 getObject() 返回的对象的类型。我们考虑过创建某种空的 Object 类,并让 getObject 返回一个指向该 Object 类实例的指针。然后,用户必须创建继承自该 Object 类的类。但是,我们仍然有一个问题,在 doSomething() Python 方法中,传入参数的对象只会暴露 Object 类的方法,而不是用户定义的派生类方法。 所以 Python 代码可以传入任何内容,然后期望稍后再将其取回,但只有 python 实现需要了解它实际上是什么? 没错! C++ 库不必知道传递给它的对象的任何信息。 【参考方案1】:首先,我们希望将您的代码变成真实且可运行的东西。我根据您展示的小代码编写了自己的 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 类型映射配对来做到这一点。总的来说,我们需要解决两个问题:
-
我们怎样才能使引用计数正常工作而不泄漏?
如果我们得到的
void*
不是真正的 PyObject*
会怎样?
如果我们一开始就假设getObject()
调用和doSomething()
调用之间存在 1:1 映射,那么引用计数相当简单,我们可以在接口中编写两个类型映射来保留对 @ 的引用987654331@,然后在需要时将其从void*
转换回(请注意,我们还通过添加 1:1 限制在此处完全回避了问题 #2)。
所以有了这两个类型图,我们的界面就变成了:
%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"
当我们这样测试它时:
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
但是,如果我们将此处的语义更改为不完全是 1:1,那么我们就会遇到问题,例如让 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";
现在哪个段错误是因为在第一次调用 doSomething
完成后引用会减少。
在这个阶段,显而易见的事情是在directorin 类型映射中添加对Py_INCREF
的调用,但这还不是全部 - 我们将从不调用释放@ 的结果现在是 987654339@,它只是在runMe()
末尾超出范围。
我倾向于解决这个问题的方法是在您的Base
界面中添加另一个调用:
virtual void cleanupThing(void* o) // Default nothing, not mandatory
有了这个,我们可以让你的 SWIG 接口实现(如果我们想要的话可以隐藏)完全在 Python 控制器内部调用。这样做的方法是使用一些 %rename
和 %ignore
以及一些宏技巧:
因此,通过对 SWIG 界面的以下调整,我们现在可以在 runMe
的第二个化身上正常工作:
%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"
从runMe
调用cleanupThing
:
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
(如果语义比简单地来回传递给同一实例的局部变量更复杂,则存在其他可能的解决方案。
【讨论】:
你好,我今晚试过了。首先,我必须实现 PyBase 构造函数才能使其工作。但是后来我遇到了一个问题,因为动态库没有某些符号。特别是,我的“Base”类在“ListcleanupThing
成为Base
内的一个完全成熟的虚拟成员函数,并使用#define` 跳过整个解决方法
抱歉,您能否详细说明一下。实际上,我的 Base 类中已经有一个 viurtalcleanupThing 方法。但是我应该删除#define 行还是代码示例第二部分的所有行?我仍然需要在某个地方进行清理。以上是关于如何在 Python 中处理 SWIG 导向器方法中的 void 指针的主要内容,如果未能解决你的问题,请参考以下文章
使用 SWIG 控制器将 python 中的 numpy 函数传递给 C++
如何在带有 Swig 的 Python 中使用 float **?
SWIG 如何在 Python 中包装 map<string,string>?