Python ctypes回调函数GC问题及模块级引用方案正确性验证
问题描述
我正在使用一个简单的ctypes模块:
% cat acme/__init__.py from acme import lowlevel
以及
% cat acme/lowlevel.py import logging _lib = cdll.LoadLibrary("libacme.so.0") def _func(name, restype, argtypes): func = getattr(_lib, name) func.restype = restype func.argtypes = argtypes return func def py_log_func(a, b, c): log_levels = { 1: logging.DEBUG, 2: logging.INFO, 3: logging.WARNING, 4: logging.ERROR, 5: logging.CRITICAL, } log = logging.getLogger(b) log.log(log_levels[a], c) return LOGFUNC = CFUNCTYPE(None, c_int, c_char_p, c_char_p) acme_log_listener_configure = _func("acme_log_listener_configure", None, [LOGFUNC]) # 设置默认监听器: acme_log_listener_configure(LOGFUNC(py_log_func))
上述代码的问题很明显:直接在调用acme_log_listener_configure时创建的LOGFUNC(py_log_func)回调对象没有被持久引用,Python垃圾回收机制可能会销毁它,导致C代码触发回调时程序崩溃。
我的问题是:下述修复方案是否正确?
# 在模块级别存储日志函数,防止Python对其进行垃圾回收: PY_LOG_FUNC = LOGFUNC(py_log_func) # 设置默认监听器: acme_log_listener_configure(PY_LOG_FUNC)
换句话说,PY_LOG_FUNC能否保证在Python进程的整个生命周期内保持有效?
回答
这个修复方案是完全正确的。
将LOGFUNC(py_log_func)赋值给模块级别的变量PY_LOG_FUNC后,该变量会和模块本身绑定,只要模块不被显式卸载,它就会在Python进程的整个生命周期内被保留,从而确保ctypes回调对象不会被垃圾回收,C代码在调用回调时始终能访问到有效的对象。
根据Python官方ctypes文档(中文翻译):
请确保在C代码使用CFUNCTYPE()对象期间保留对它们的引用。ctypes不会保留这些引用,如果不手动保留,它们可能会被垃圾回收,导致回调时程序崩溃。
模块级变量的生命周期与Python进程绑定,除非你主动删除PY_LOG_FUNC或者卸载整个acme.lowlevel模块,否则它会一直保持有效,完全满足C代码对回调对象的持久化需求。
内容的提问来源于stack exchange,提问作者malat
相关产品推荐
相关产品推荐

