我正在为一个C库创建Python绑定。
在C中,使用函数的代码将如下所示:
Ihandle *foo;
foo = MethFunc();
SetArribute(foo, 's');我正试着把它放到Python中。其中有可以在Python代码中使用的MethFunc()和SetAttribute()函数:
import mymodule
foo = mymodule.MethFunc()
mymodule.SetAttribute(foo)到目前为止,我返回函数的C代码如下所示:
static PyObject * _MethFunc(PyObject *self, PyObject *args) {
return Py_BuildValue("O", MethFunc());
}但崩溃会导致失败(无错误)
我也尝试过return MethFunc();,但是失败了。
我如何返回函数foo (或者如果我试图实现的是完全错误的,我应该如何将MethFunc()传递给SetAttribute())?
发布于 2018-07-11 05:21:12
这里的问题是,MethFunc()返回一个IHandle *,但您告诉Python将其视为PyObject *。这些可能是完全不相关的类型。
引用( PyObject * )(或者您或Python定义的任何以适当的引用宏开头的struct )从指向引用计数和类型的指针开始,Python要处理的任何对象的第一件事就是处理这些指针。所以,如果你给它一个对象,而不是以两个int开头,Python会尝试访问0x00020001或类似类型的类型,这几乎肯定会出现段错误。
如果需要传递指向某个C对象的指针,则必须将其包装在Python对象中。有三种方法可以做到这一点,从hackiest到最可靠。
首先,您可以将IHandle *转换为size_t,然后对其执行PyLong_FromSize_t操作。
这是非常简单的实现。但这意味着这些对象看起来将与Python端的数字完全相同,因为这就是它们的全部。
显然,您不能将方法附加到这个数字;相反,您的API必须是一个接受数字的自由函数,然后将该数字转换回IHandle*并调用一个方法。
例如,它更像C的stdio,你必须将stdin或f作为参数传递给fread,而不是Python的io,后者在sys.stdin或f上调用方法。
但更糟糕的是,因为没有类型检查,无论是静态的还是动态的,都不能防止某些Python代码意外地向您传递数字42。然后将其强制转换为IHandle *,并尝试取消引用,从而导致段错误…
如果您希望Python的垃圾收集器能够帮助您了解对象何时仍被引用,那么您就大错特错了。您需要让用户手动跟踪号码,并在使用完号码后调用一些CloseHandle函数。
真的,这并不比从ctypes访问你的代码好多少,所以希望这能激励你继续阅读。
更好的解决方案是将IHandle *转换为void *,然后对其执行PyCapsule_New。
如果你还没有读过capsules,你至少需要浏览一下主要章节。但基本思想是它将void*包装为Python对象。
因此,它几乎和传递数字一样简单,但解决了大多数问题。胶囊是不透明的值,您的Python用户不能无意中对其进行算术运算;他们不能向您发送42来代替胶囊;您可以附加一个函数,在最后一个对胶囊的引用消失时调用该函数;您甚至可以给它一个好名字,以便在repr中显示出来。
但是你仍然不能将任何行为附加到胶囊上。
因此,如果mymodule是一个胶囊,那么你的API仍然必须是一个MethSetAttribute(mymodule, foo)而不是mymeth.SetAttribute(foo),就像它是一个int一样。(除了现在它是类型安全的。)
最后,您可以为包含IHandle *的结构构建新的Python扩展类型。
这是更多的工作。如果你还没有读过关于Defining Extension Types的教程,你需要通读整个章节。
但这意味着您有一个实际的Python类型,以及与之相关的所有内容。
您可以给它一个SetAttribute方法,Python代码可以直接调用该方法。你可以给它任何你想要的__str__和__repr__。你可以给它一个__doc__。Python代码可以做isinstance(mymodule, MyMeth)。诸若此类。
如果你愿意使用C++,或者D,或者Rust而不是C,有一些很棒的库(PyCxx,boost::python,Pyd,rust-python等)。它可以为您完成大部分的样板工作。您只需声明您想要一个Python类,以及您希望将其属性和方法绑定到C属性和方法的方式,您就会得到一些可以像C++类一样使用的东西,只是它实际上是一个隐藏的PyObject *。(它甚至会通过RAII为你解决所有的重新计算问题,这将省去你无休止的周末调试段错误和内存泄漏…)
或者您可以使用Cython,它允许您用一种语言编写C扩展模块,这种语言基本上就是Python语言,但是扩展到了与C代码的接口。因此,您的包装类只是一个class,但是具有一个特殊的私有cdef属性来保存IHandle *,并且您的SetAttribute(self, s)可以使用该私有属性调用C SetAttribute函数。
或者,根据用户的建议,您也可以使用SWIG为您生成C绑定。对于简单的情况,它非常简单--只需将您的back提供给它,它就会返回构建Python的代码。对于不太简单的情况,我个人发现它比PyCxx要痛苦得多,但如果你还不了解C++,它的学习曲线肯定会更低。
https://stackoverflow.com/questions/51273804
复制相似问题