You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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...")

关键改动解释

  1. 保留回调的强引用:在类的__init__里新增了self._callback_refs列表,每次创建回调包装器后都把它加进去。这样Python的垃圾回收器就不会把这些回调对象当作“无用内存”回收,确保C DLL调用时回调地址始终有效。
  2. 修正start函数的argtypes:原代码里lib.start.argtypes = None是错误用法——ctypes规定无参数的函数,argtypes必须设为空元组(),否则可能导致参数解析错误,间接引发内存问题。
  3. 主线程保持存活:如果主线程直接结束,ctr这个Controler实例会被回收,连带里面的_callback_refs列表也会被销毁,回调还是会被回收。所以加了一个循环让主线程一直运行,直到你按Ctrl+C中断。

另外,如果你想更简洁地用类方法作为回调,也可以用ctypes.CFUNCTYPE结合functools.partial绑定实例,但上面的方案是对你原有代码改动最小的,最容易理解和验证。

内容的提问来源于stack exchange,提问作者Sicks

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 18:59:05