如何在Python ctypes中使方法作为回调?
我有一个C语言的接口,正在用Python的ctypes包来连接。整体运行得很好,但有一个小问题。
为了注册一些函数作为回调,用来处理通知,我调用了这个函数:
void RegisterNotifyCallback( int loginId, int extraFlags, void *(*callbackFunc)(Notification *))
所以在Python中,我的代码看起来是这样的:
CALLBACK = ctypes.CFUNCTYPE(None, ctypes.POINTER(Notification))
func = CALLBACK(myPythonCallback)
myLib.RegisterNofityCallback(45454, 0, func)
如果myPythonCallback的值是一个普通函数(也就是说,不是类的方法),那就没问题,能正确调用回调函数并传入正确的参数。但如果它是一个类的方法,虽然也能调用这个方法,但在方法执行完后就会出现段错误(segfault)。
补充说明
为了更清楚地说明,当我说函数类型时,我是指myPythonCallback被定义为一个普通函数,
def myPythonCallback(Notification):
...do something with the Notification object i get passed in
而当我说它是MethodType时,我指的是一个类的方法:
class MyClass(object):
def myPythonCallback(self, Notification):
...do something with Notification
在第二种情况下就会出问题。
有没有简单的方法可以解决这个问题?或者有人能给我解释一下为什么会发生这种情况吗?
非常感谢,
Al。
补充说明
进一步用GDB调试时,我得到了这个:
Program received signal SIGSEGV, Segmentation fault. [Switching to Thread 1544567712 (LWP 5389)] 0x555ae962 in instancemethod_dealloc (im=0x558ed644) at Objects/classobject.c:2360 2360 Objects/classobject.c: No such file or directory.
in Objects/classobject.c (gdb) backtrace
#0 0x555ae962 in instancemethod_dealloc (im=0x558ed644) at Objects/classobject.c:2360
#1 0x555f6a61 in tupledealloc (op=0x5592010c) at Objects/tupleobject.c:220
#2 0x555aeed1 in instancemethod_call (func=0x558db8ec, arg=0x5592010c, kw=0x0) at Objects/classobject.c:2579
#3 0x5559aedb in PyObject_Call (func=0x558db8ec, arg=0x5592c0ec, kw=0x0) at Objects/abstract.c:2529
#4 0x5563d5af in PyEval_CallObjectWithKeywords (func=0x558db8ec, arg=0x5592c0ec, kw=0x0) at Python/ceval.c:3881
#5 0x5559ae6a in PyObject_CallObject (o=0x0, a=0x0) at Objects/abstract.c:2517
传入instancemethod_dealloc的值是:
(gdb) x 0x558ed644 0x558ed644: 0x00000000
你觉得这个问题能解决吗?
3 个回答
有人说过,程序崩溃(segfault)可能是因为方法的垃圾回收问题,你需要保存对它的引用。我是这样解决缺少self参数和垃圾回收问题的。
CALLBACK = ctypes.CFUNCTYPE(None, ctypes.POINTER(Notification))
class MyClass(object):
def myPythonCallback(self, Notification):
...do something with Notification
def __init__(self):
self.myPythonCallback = CALLBACK(self.myPythonCallback)
myLib.RegisterNofityCallback(45454, 0, self.myPythonCallback)
每次创建这个类的新实例时,都会生成一个绑定的方法(self.myPythonCallback),所以每个注册的回调函数都是特定于这个类实例的。我们在类中保存了对回调函数的引用,这样它就不会被垃圾回收掉。CFUNCTIONTYPE回调函数的执行方式和普通全局函数一样,<function_CFUNCTYPE>
,而它内部的回调就是绑定的方法<bound method myPythoncCallback of MyClass 0xfffabca0>
。绑定的方法会使用self来执行。
注意:虽然看起来你可以直接使用@decorator语法,但那样是行不通的。方法上的装饰器会装饰原始方法,然后再绑定到类上,所以效果是self.myPythonCallback会变成一个<bound CFUNCTYPE on MyClass>
,调用的是一个普通方法<function myPythonCallback>
。
由@David-Heffernan提供的闭包解决方案给了我灵感。
这可能是一个关于ctypes的错误,涉及到为方法和函数设置堆栈时的神秘机制。
你有没有试过通过lambda或者函数来做一个包装?
使用lambda:
func = CALLBACK(lambda x: myPythonCallback(x))
使用函数:
def wrapper(x): myPythonCallback(x)
func = CALLBACK(wrapper)
还有第三种方法是使用闭包。如果你是在类里面设置回调,那么下面定义的方法应该会因为作用域的原因继承“self”这个参数——这让它虽然是普通函数,但表现得像个类方法。
class Foo:
def __init__(self):
def myPythonCallback(Notification):
print self # it's there!
pass # do something with notification
func = CALLBACK(myPythonCallback)
myLib.RegisterNofityCallback(45454, 0, func)
据我所知,你不能直接调用一个绑定的方法,因为它缺少了一个叫做self的参数。为了处理这个问题,我使用了一个闭包,像这样:
CALLBACK = ctypes.CFUNCTYPE(None, ctypes.POINTER(Notification))
class MyClass(object):
def getCallbackFunc(self):
def func(Notification):
self.doSomething(Notification)
return CALLBACK(func)
def doRegister(self):
myLib.RegisterNofityCallback(45454, 0, self.getCallbackFunc())