如何使用python ctypes获取方法作为回调?

时间:2011-08-31 15:54:13

标签: python ctypes

我有一个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

你认为这是可以解决的吗?

3 个答案:

答案 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提供的封闭式解决方案是我的灵感