SWIG将C库接口接入Python(SWIG生成的类使用起来很繁琐)
我正在使用SWIG工具来为我的C语言库生成Python语言的接口。虽然我已经成功构建了接口并导出了数据结构,但在使用这个库时遇到了一些麻烦。
比如说,C语言的头文件里有一些数据类型和函数的原型,如下所示:
struct MyStruct
{
/* fields */
}
struct MyStruct * MYSTRUCT_Alloc(void);
void MYSTRUCT_Free(struct MyStruct *);
struct MyStruct * MYSTRUCT_Clone(const struct MyStruct *);
int MYSTRUCT_Func1(const struct MyStruct *, const int);
/* and so on */
在我的SWIG接口文件中,我同时导出了这些函数和MyStruct数据类型。假设我的Python扩展模块叫做foobar,我可以这样写Python脚本:
#import foobar as fb
# The line below creates a Python class which is a wrapper to MyStruct. HOWEVER I cannot pass this class to a function like MYSTRUCT_Func1 until I have initialized it by calling MYSTRUCT_Alloc ...
ms = fb.MyStruct
# This will fail (throws a Python exception)
# ret = fb.MYSTRUCT_Func1(ms, 123)
# However this works
ms = fb.MYSTRUCT_Alloc()
ret = fb.MYSTRUCT_Func1(ms, 123)
不过,声明一个对象然后再给它赋值指针使用起来非常麻烦,而且容易出错。有没有更好的方法来使用SWIG生成的类呢?我在想,是否可以封装更高级的类(或者继承SWIG生成的类),这样就能自动处理对象的创建和销毁,同时提供一些明显的成员函数,比如MYSTRUCT_Func1()。
但是,如果我真的去封装或继承SWIG生成的类,我就不确定能否把这些新类传递给那些需要C结构体指针的C API函数。我不能直接修改SWIG生成的类(或者说不应该这样做),这显而易见。
那么,解决这个问题的最佳方法是什么呢?有没有一种更符合Python风格的方式来创建和销毁对象,同时又能直接将指针传递给暴露的C函数?
3 个回答
你可以把用SWIG生成的模块命名为_foobar
,然后再写一个纯Python的模块foobar
,这个模块负责定义Python需要的接口。例如,M2Crypto
就是一个openssl
的封装,它就是这样做的。
另外一个选择是使用Cython,直接在C语言中创建接口:
cdef class MyStruct:
cdef MyStruct_ptr this
def __cinit__(self):
self.this = MYSTRUCT_Alloc();
if self.this is NULL:
raise MemoryError
def __dealloc__(self):
if self.this is not NULL:
MYSTRUCT_Free(self.this)
def func1(self, n):
return MYSTRUCT_Func1(self.this, n)
这样就会创建一个Python的C扩展类型MyStruct
,你可以在Python中这样使用它:
ms = Mystruct()
print(ms.func1(123))
想要完整的例子,可以查看wrap Person.h。
在我看来,在Python这边写一个包装器是个不错的主意,不明白你为什么觉得这样做不行。
class MyStructWrapper:
def __init__(self):
self.ms = fb.MYSTRUCT_Alloc()
def __del__(self):
fb.MYSTRUCT_Free(self.ms)
def func1(self, arg):
return fb.MYSTRUCT_Func1(self.ms, arg)
如果你需要访问结构体里的成员,可以用 self.ms.member
,或者写一些获取和设置的函数。
你也可以把你的 clone
函数放进这个设计里。
编辑: 关于你的评论,假设你有一个全局函数,它接受一个指向 MyStruct
的指针:
int gFunc(MyStruct* ms);
在Python这边,你可以这样写一个包装器:
def gFuncWrapper(mystruct):
return fb.gFunc(mystruct.ms)
希望这对你有帮助。
你的代码:
ms = fb.MyStruct
# This will fail (throws a Python exception)
# ret = fb.MYSTRUCT_Func1(ms, 123)
只给 ms
这个类赋了值,并没有给类的实例赋值,这就是为什么注释掉的那一行会出错。下面的代码应该可以正常工作:
ms = fb.MyStruct()
ret = fb.MYSTRUCT_Func1(ms, 123)