我使用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上更有经验。提前感谢您的回复。
答案 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)