Python ctypes如何正确定义并解引用结构体中带结构体参数的C回调函数指针
正确实现方案
报错根因
之前的类型错误来自三个核心问题:
- 把普通Python函数
CALLBACK_FUNC当成ctypes支持的类型使用,ctypes.cast的目标参数必须是ctypes认可的原生类型、结构体类型、或是通过CFUNCTYPE/WINFUNCTYPE生成的函数指针类型,普通Python函数不属于该范畴。 - 错误地在目标类型外层套了
POINTER:CFUNCTYPE生成的类型本身就对应C侧的函数指针,再加POINTER会变成指向函数指针的二级指针,和实际类型不匹配。 - 没有处理结构体自引用的循环依赖:C侧回调的入参是指向结构体自身的指针,而结构体本身又包含该回调类型,按常规顺序类定义会触发未定义引用错误。
分步实现代码
- 第一步:前置空定义结构体类,解决循环依赖
import ctypes # 先空定义结构体类,让Python预先识别该类型,避免后续回调定义时的未定义引用错误 class INSTANCE_STRUCT(ctypes.Structure): pass
- 第二步:用
CFUNCTYPE定义和C侧完全匹配的回调类型
C侧的回调签名是int (*callback)(struct instance *),ctypes对应该类型的定义规则为:第一个参数指定返回值类型,后续依次排列所有入参的类型:
# 回调类型定义:返回c_int,入参为指向INSTANCE_STRUCT的指针 CALLBACK = ctypes.CFUNCTYPE( ctypes.c_int, ctypes.POINTER(INSTANCE_STRUCT) )
注意:如果是Windows平台通过stdcall调用约定导出的C函数,需要把
CFUNCTYPE替换为WINFUNCTYPE,否则会出现栈不平衡的崩溃问题;Linux/macOS下默认cdecl调用约定,直接用CFUNCTYPE即可。
- 第三步:补全结构体的字段定义
不需要把函数指针成员声明为c_void_p,直接使用上面定义好的CALLBACK类型,ctypes会自动按指针大小匹配字段偏移:
INSTANCE_STRUCT._fields_ = [ ("x", ctypes.c_int), ("f", CALLBACK) ]
- 第四步:拿到C侧的结构体实例后直接调用成员函数
将C侧传入的指针转换为对应结构体类型后,访问f成员本身就是可调用对象,不需要额外做类型转换:
# 将C侧malloc返回的结构体指针转换为对应类型 instance = ctypes.cast(pointer_to_structure, ctypes.POINTER(INSTANCE_STRUCT)) # 直接读取x字段,和之前的运行结果一致 print(instance.contents.x) # 直接调用f成员:把当前结构体的指针作为入参传入即可 call_result = instance.contents.f(instance) print(call_result)
兼容已有的c_void_p写法
如果你已经把结构体成员f声明为c_void_p,拿到对应内存地址后,只需要把之前的cast目标换成已定义的CALLBACK类型即可,不需要外层套POINTER:
# 之前通过c_void_p拿到的f的内存地址(即你运行脚本输出的大整数) f_addr = instance.contents.f # 直接转换为可调用的回调对象 callable_f = ctypes.cast(f_addr, CALLBACK) # 正常传入结构体指针调用即可 call_result = callable_f(instance)
内容的提问来源于stack exchange,提问作者Patryk Szczypień
相关产品推荐
相关产品推荐

