SWIG将C库接口接入Python(SWIG生成的类使用起来很繁琐)

2 投票
3 回答
1126 浏览
提问于 2025-04-17 05:50

我正在使用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 个回答

0

你可以把用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

2

在我看来,在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)

希望这对你有帮助。

2

你的代码:

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) 

撰写回答