【问题标题】:SWIG interfacing C library to Python (SWIG generated classes are cumbersome to use)SWIG 将 C 库连接到 Python(SWIG 生成的类使用起来很麻烦)
【发布时间】:2011-11-08 01:16:09
【问题描述】:

我正在使用 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 生成的类)以自动处理对象的创建和销毁(以及提供一些 OBVIOUS 成员函数,如 MYSTRUCT_Func1()。

但是,如果我对 SWIG 生成的类进行包装/子类化,那么我不确定是否可以将新类传递给期望指向 C 结构的指针的 C API 函数。我不能直接修改 SWIG 生成的类(或者至少我不应该)——出于显而易见的原因。

解决此问题的最佳方法是什么?创建/销毁对象的更 Pythonic 方式,同时能够将指针直接传递给暴露的 C 函数?

【问题讨论】:

  • 考虑使用 ctypes 而不是 SWIG 来访问您的库。

标签: python c swig python-extensions


【解决方案1】:

您的代码:

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) 

【讨论】:

  • 啊,这是我出错的地方(我是 python 新手)。它现在可以正常工作。最后一件事。我想向 MyStruct() 类添加新的行为和方法 - 但至关重要的是,我仍然希望能够将“增强类”传递给需要原始指针的函数(目前,我可以传递自动生成的 MyStruct 类到我导出的需要原始指针的 C 函数)。显然,我不能直接修改生成的代码,所以我尝试将 MyStruct 子类化,但是当我将新类传递给我的 C 函数时,我得到一个 TypeError 异常参数 1 类型为 'struct MyStruct *'
  • 我想为 MyStruct 提供一个包装器的另一个原因是我可以正确地销毁它。它是一个嵌套结构,一些函数实际上是从堆中分配内存 - 所以它需要被正确销毁,以避免内存泄漏
  • 您是否考虑过编写一个 C++ 类并使用 SWIG 来公开它?然后您可以定义适当的构造函数和析构函数并添加其他方法。 SWIG 将把类扁平化为 C 方法,并提供一个 Python 包装类,因此它再次像 Python 中的一个类。
  • @Mark 说的是个好主意。我对这个问题的回答建议在 Python 方面做这样的事情。如果你觉得在 C++ 上工作比在 Python 上工作更舒服,那么在 C++ 方面做会更好。
  • 马克和米格尔。我认为你们俩都是对的。我已经围绕 C 库编写了 C++ 包装器,以便在 Python 中轻松使用。我会接受 Mark 的回答,因为他指出了如何使用 Python 中的库,并建议使用 C++ 包装器来简化事情。
【解决方案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 或编写getter 和setter 来实现。

您还可以将您的 clone 函数放入此设计中。

编辑:关于您的评论,假设您有一个全局函数,它接受指向MyStruct 的指针:

int gFunc(MyStruct* ms);

在Python端,你可以写一个包装器如下:

def gFuncWrapper(mystruct):
    return fb.gFunc(mystruct.ms)

我希望这会有所帮助。

【讨论】:

  • 我也有免费(非成员)函数,它们期望 C struct ptrs 作为参数。目前,我可以将 SWIG 生成的类传递给那些公开的 C 函数。我可以类似地将 MyStructWrapper 传递给这些函数吗?如果没有,是否有我可以使用的“特殊方法”,例如我可以在包装类上实现的 value 以便它在传递给需要 C 结构指针的函数时返回原始指针?
  • 当然,您需要为全局函数创建 Python 包装函数。这些将采用 Python 包装类,但将 ms 成员向下发送到 C 函数。我会用一个例子来更新我的答案。
【解决方案3】:

您可以将 SWIG 生成的模块命名为 _foobar 并编写一个纯 Python 模块 foobar 来定义必要的 Python 接口,例如 M2Cryptoopenssl 包装器遵循这种方法。

另一种选择是使用 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-05
    • 1970-01-01
    相关资源
    最近更新 更多