Python Ctypes注册回调函数

时间:2016-07-08 22:22:55

标签: python callback ctypes

我使用Python和ctypes遇到了一些非常奇怪的事情。我正在使用Python 3.4.3。首先,项目的一些背景:

我已经从C代码编译了一个自定义dll。我正在使用ctypes与dll连接。 C库与某些自定义硬件连接。有时,硬件会生成中断并将其传递到计算机上的C库。在C API中,有一个原型void register_callback(int addr, void (*callback)(void))的函数。我有一个回调函数指针数组,它们被初始化为NULL。调用此函数时,index addr处的回调函数指针设置为callback,如下所示:callbacks[addr] = callback;

当用户使用Python编程时,他们会从模拟不同硬件部分(例如按钮或RGB LED)的类中实例化对象。然后,他们可以编写自定义回调函数并调用button.register_callback(func)(假设他们有一个名为button的Button对象),它调用C库中的register_callback函数。现在,当按下按钮并生成中断时,C库将调用适当的回调函数(即callbacks[addr]();)。

现在,奇怪:

在Python中,我在Python中首次尝试使用register_callback方法看起来像这样:

class Obj:
    def __init__(self, name):
        # Initialize stuff

    def register_callback(self, func):
        CB_T = ctypes.CFUNCTYPE(None)
        cb_ptr = CB_T(func)
        host_api.register_callback(self.addr, cb_ptr) # host_api is the loaded dll

主要是:

def cb1():
    print("cb1")

def cb2():
    print("cb2")

def main(argv):
    # Initialization stuff
    # Now create the objects and register the callbacks:
    obj = Obj_module.Obj()
    obj2 = Obj_module.Obj()
    obj.register_callback(cb1)
    obj2.register_callback(cb2)

    while True:
        pass

当我跑步时,无论我按哪个按钮,都只打印“cb2”。真正奇怪的是,当我改变我注册回调的顺序时:

    obj2.register_callback(cb2)
    obj.register_callback(cb1)

正在打印“cb1”,无论我按下哪个按钮!在C库中,我验证(通过printf)根据按钮设置和调用不同的回调函数指针,但是相同的函数指针被传递给C register_callback函数。

我能够通过在register_callback方法中添加一行来解决问题:

def register_callback(self, func):
    CB_T = ctypes.CFUNCTYPE(None)
    cb_ptr = CB_T(func)
    (ctypes.cast(cb_ptr, ctypes.POINTER(ctypes.c_int)))
    host_api.register_callback(self.addr, cb_ptr)

显然,将cb_ptr转换为ctypes POINTER修复了问题 - 传入了不同的函数指针,并且我成功地看到“cb1”或“cb2”打印,具体取决于我按下的按钮。

我的问题是,为什么?为什么在原始代码中传递相同的函数指针,为什么它会根据我注册回调的顺序而改变,为什么将cb_ptr转换为ctypes POINTER以确保函数指针不同?

我是Python的初学者,但我在C上更有经验。提前感谢您的回复。

1 个答案:

答案 0 :(得分:3)

您的cb_ptr正在被垃圾收集。来自documentation

  

确保只要它们保留对CFUNCTYPE()对象的引用   用于C代码。 ctypes没有,如果你不这样做,他们可能会   垃圾收集,在回调时崩溃你的程序。

在此代码示例中,如果注释掉行ptrs.append(cb_ptr),则cb_ptr实例(在我的计算机上)Obj实例的位置相同。取消注释该行会产生两个内存位置。

import ctypes

ptrs = []

class Obj:
    def __init__(self):
        pass

    def register_callback(self, func):
        CB_T = ctypes.CFUNCTYPE(None)
        cb_ptr = CB_T(func)
        ptrs.append(cb_ptr)
        print(cb_ptr)

def cb1(): print("cb1")

def cb2(): print("cb2")

def main(argv):
    obj = Obj()
    obj2 = Obj()
    obj.register_callback(cb1)
    obj2.register_callback(cb2)

main(None)