我有一个C api,我正在与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的值是FunctionType(即不是方法),它可以正常工作并使用正确的参数调用回调函数。如果它是一个MethodType,它仍会调用方法,但一旦方法完成,就会出现段错误。
修改的
澄清一下,当我说函数类型时,我的意思是将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
它在第二种类型上爆炸。
这周围有一个轻松的地方吗?或者有人可以向我解释为什么会发生这种情况?
非常感谢, 人
修改的
使用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
你认为这是可以解决的吗?
答案 0 :(得分:9)
据我所知,你不能调用绑定方法,因为它缺少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())
答案 1 :(得分:3)
这可能是一个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)
答案 2 :(得分:0)
有人说过,但段错误可能是由方法的垃圾回收引起的,您必须存储引用。我既解决了缺少的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提供的封闭式解决方案是我的灵感