使用ctypes将类方法作为API回调函数时出现内存访问异常问题求助
问题根源与修复方案
我一眼就看出问题所在了——这是Python回调函数被垃圾回收引发的内存访问异常!
你在InitStatusmessages里定义的Statusmessages是个局部函数,返回的callback_Statusmessages包装对象没有被长期持有。当InitStatusmessages执行完毕后,Python的垃圾回收器会把这个局部函数(以及对应的ctypes包装器)给回收掉。此时C DLL再尝试调用这个回调的内存地址,自然会触发SEH或者AccessViolation异常——因为那块内存已经不属于你的回调函数了。
下面是修复后的完整代码,我会逐点说明关键改动:
import ctypes as ct from threading import Thread import time callback_Statusmessages = ct.CFUNCTYPE(None, ct.c_char_p) class Controler(): lib = ct.CDLL("Test.dll") lib.Setcallbacks.argtypes = (callback_Statusmessages, callback_Statusmessages, callback_Statusmessages) lib.Setcallbacks.restype = None lib.start.argtypes = () # 修正:无参数函数的argtypes必须设为空元组,不能是None lib.start.restype = ct.c_int def __init__(self): # 新增:用列表保存回调对象的强引用,防止被GC回收 self._callback_refs = [] def InitStatusmessages(self): def Statusmessages(message): print("===================================================================================================") print("Message: ", message.decode()) print("===================================================================================================") time.sleep(.1) # just to slow it down. # 创建回调包装器 cb = callback_Statusmessages(Statusmessages) # 把包装器存入实例列表,保留引用 self._callback_refs.append(cb) return cb def Start(self): # 先创建并保存三个回调的引用 cb1 = self.InitStatusmessages() cb2 = self.InitStatusmessages() cb3 = self.InitStatusmessages() self.lib.Setcallbacks(cb1, cb2, cb3) self.thread = Thread(target=self.lib.start) self.thread.start() if __name__ == "__main__": ctr = Controler() ctr.Start() # 新增:让主线程保持运行,否则程序退出会回收Controler实例和回调引用 try: while True: time.sleep(1) except KeyboardInterrupt: print("Exiting gracefully...")
关键改动解释
- 保留回调的强引用:在类的
__init__里新增了self._callback_refs列表,每次创建回调包装器后都把它加进去。这样Python的垃圾回收器就不会把这些回调对象当作“无用内存”回收,确保C DLL调用时回调地址始终有效。 - 修正
start函数的argtypes:原代码里lib.start.argtypes = None是错误用法——ctypes规定无参数的函数,argtypes必须设为空元组(),否则可能导致参数解析错误,间接引发内存问题。 - 主线程保持存活:如果主线程直接结束,
ctr这个Controler实例会被回收,连带里面的_callback_refs列表也会被销毁,回调还是会被回收。所以加了一个循环让主线程一直运行,直到你按Ctrl+C中断。
另外,如果你想更简洁地用类方法作为回调,也可以用ctypes.CFUNCTYPE结合functools.partial绑定实例,但上面的方案是对你原有代码改动最小的,最容易理解和验证。
内容的提问来源于stack exchange,提问作者Sicks
相关产品推荐
相关产品推荐

