如何在 Python 中的 SWIG Director 方法中处理空指针
How to handle void pointers in SWIG director methods in Python
我正在使用SWIG将C++库包装为Python库。 C++库公开抽象类供用户从中继承,因此我们使用 SWIG 中的控制器来处理。它基本上可以正常工作(经过一些调整)。
一个问题是这个C++类有两个方法,如下所示:
class Base {
void* getObject();
void doSomething(void* o);
}
用户应该实现这些方法,然后用户在getObject()中返回的对象传递给doSomething()方法。
问题是,在通过SWIG时,Python中的doSomething()方法会收到一个包装类型为"void*"的SwigPyObject,因此我们不能像我们希望的那样使用原始的Python对象方法。 而且投射不是一种选择,因为它是 Python(或者是吗?
有人有什么见解吗?
我在这里和那里发现了一些相关的问题,但似乎没有一个能准确地解决我的情况,我已经尝试了很多事情来解决它,但没有任何成功。
如果您需要更多详细信息,请告诉我,我会提供。
多谢!
首先,我们希望将你的代码变成真实且可运行的东西。我根据您展示的小代码编写了自己的 test.hh,让我们可以稍微练习这个设计:
class Base {
public:
void runMe() {
std::cerr << "Getting objectn";
void *result = getObject();
std::cerr << "Got: " << result << "n";
doSomething(result);
std::cerr << "Did a thingn";
}
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*
。我们可以通过导演和导演输出类型图配对来做到这一点。从广义上讲,我们需要解决两个问题:
- 我们如何使引用计数正常工作而不泄漏?
- 如果我们得到的
void*
不是真正的PyObject*
,会发生什么?
如果我们首先假设getObject()
调用和doSomething()
调用之间存在 1:1 映射,那么引用计数相当简单,我们可以在界面中编写两个类型图,保留对PyObject
的引用,然后在需要时将其从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 objectn";
void *result = getObject();
std::cerr << "Got: " << result << "n";
doSomething(result);
std::cerr << "Second timen";
doSomething(result);
std::cerr << "Did a thingn";
}
现在出现段错误,因为在第一次调用doSomething
完成后引用会递减。
在这个阶段,显而易见的事情是在 directorin typemap 中添加对Py_INCREF
的调用,但这还不是故事的全部 - 我们现在永远不会将发布称为getObject()
的结果,它只是在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 objectn";
void *result = getObject();
std::cerr << "Got: " << result << "n";
doSomething(result);
std::cerr << "Second timen";
doSomething(result);
std::cerr << "Did a thingn";
cleanupThing(result);
}
现在运行时确实会得到:
Getting object
Got: 0x7ff65dccfd08
[1, 2, 3]
Second time
[1, 2, 3]
Did a thing
(存在其他可能的解决方案,特别是如果语义比简单地来回传递到同一实例的局部变量更复杂)。
- 在C#中处理C++指针而不使用unsafe的最佳方法
- 处理多个异常集合的C++方法
- 在C++中样板"冷/never_inline"错误处理技术的最佳方法是什么?
- CPU 瓶颈;处理具有许多非静态对象的 3D 场景渲染的简单方法
- 使用 Git 处理 C++ Visual Studio 2019 解决方案的外部依赖项源代码管理的最佳方法是什么?
- Visual C++: MSVC vs. GCC+CLANG: 处理 lambda 捕获类成员变量,正确的方法是什么?
- 有没有更好的方法来处理异常? try-catch块真的很丑
- 处理编译器关于可能丢失数据的警告的最优雅方法是什么
- 处理影响跨不同线程共享对象的定时回调的最佳方法是什么?
- 在自定义 std::vector-like 容器中处理指针和非指针模板类型的最佳方法是什么?
- 处理从列表中删除指向对象的指针的"healthy"方法是什么?
- 哪种方法更适合处理虚拟析构函数?
- 使输出流式处理运算符适用于 boost::variant<std::vector<int>、int、double 的正确方法是什么>
- Tesseract/Lebonica处理单页和多页图像的正确方法
- 在 if 语句中处理多个 or 的更优雅的方法是什么
- Java(或C++)如何处理接口中定义的方法的调用
- 以C++读取文件并处理可能的错误的便携式方法
- 是否有可以处理方法调用依赖关系的设计模式?
- 如何将信号处理程序添加为方法
- 在2018年使用C++处理Unicode的正确方法是什么?