Python ctypes封装C++ API如何传NULL结构体指针并获取更新值
Python ctypes封装C++ API 传NULL指针及同步结构体值方案
核心问题
- 调用
op_init接口时需传入NULL结构体指针,需明确Python侧传参实现 - 接口传入非空结构体指针时,会将从配置文件读取到的值写入指针指向内存,需明确如何同步动态库更新的结构体数值到Python对象
前置:C侧接口与实现参考
头文件定义 cpp_header.hpp
typedef enum { E_FLAG_ON = 0, E_FLAG_OFF } option; typedef struct { float *a; float b; char *file_path; option flag; } inputs; // API int op_init(const inputs*);
接口实现逻辑 Implementation.cpp
int op_init(const inputs* usr_ptr) { inputs local_struct; internal_opration_read_set(&local_struct) { // 读取输入文件更新local_struct的值 } if (usr_ptr != NULL) { internal_opration_update_set(usr_ptr, &local_struct) { // 仅当传入非空指针时,将读取到的配置写入用户传入的结构体 } } }
C侧NULL传参调用示例 test.cpp
int main() { inputs *usr_cfg = NULL; op_init(usr_cfg); }
现有ctypes初步封装代码 ctypes_imple.py
from ctypes import * class inputs(Structure): _fields_ = [('a', POINTER(c_float)), ('b', c_float), ('file_path', c_char_p), ('flag', option)] # loading so file so_lib = CDLL('some so') # how we can initialize NULL structure pointer here? so_lib.op_init() # how to send structure pointer as argument?
完整实现方案
1. 补全类型定义,显式声明函数签名
首先补全枚举类型定义,加载动态库后必须显式设置函数的参数类型和返回值类型,否则ctypes默认按int传参,64位环境下会出现指针截断、传参崩溃问题:
from ctypes import * # 对应C侧option枚举,C枚举默认底层类型为int class option(c_int): E_FLAG_ON = 0 E_FLAG_OFF = 1 class inputs(Structure): # 字段顺序、类型必须和C侧定义完全一致,否则内存偏移错误会导致值错乱 _fields_ = [ ('a', POINTER(c_float)), ('b', c_float), ('file_path', c_char_p), ('flag', option) ] # 加载动态库,替换为实际so文件路径 so_lib = CDLL('./lib_your_api.so') # 声明op_init的返回值类型为int so_lib.op_init.restype = c_int # 声明op_init的参数类型为指向inputs结构体的指针 so_lib.op_init.argtypes = [POINTER(inputs)]
2. 传入NULL结构体指针的实现
ctypes中None直接对应C侧的NULL指针,传None即可实现和C侧传NULL完全一致的效果:
# 等价于C侧 inputs *usr_cfg = NULL; op_init(usr_cfg); ret_code = so_lib.op_init(None)
3. 同步动态库更新的结构体值
ctypes定义的结构体实例内存布局和C侧完全兼容,只需在Python侧实例化inputs对象,将对象指针传入接口,C侧写入内存后,Python侧直接访问实例属性即可拿到更新后的值,无需额外同步操作:
# 实例化结构体对象 usr_cfg = inputs() # 传入结构体指针,C侧会将读取到的配置写入该对象对应的内存 ret_code = so_lib.op_init(byref(usr_cfg)) # 调用完成后直接读取属性即可获取更新后的值 print(f"字段b的值: {usr_cfg.b}") print(f"flag字段的值: {usr_cfg.flag.value}") # 枚举类型取.value拿到实际数值 # 指针类型字段访问注意判空,c_char_p为bytes类型需解码为字符串 if usr_cfg.a: print(f"指针a指向的浮点值: {usr_cfg.a[0]}") if usr_cfg.file_path: print(f"配置文件路径: {usr_cfg.file_path.decode('utf-8')}")
注意事项
- 禁止省略
argtypes和restype的声明,这是ctypes封装最常见的出错点- 如果C侧结构体用了
#pragma pack等特殊内存对齐设置,需要在Python结构体类中设置_pack_属性和C侧对齐值一致- 结构体中
a、file_path是C侧分配内存的指针,除非API文档明确要求调用指定释放接口,否则不要在Python侧手动释放,避免内存错误- 推荐用
byref()传递结构体指针,比pointer()性能更高
内容的提问来源于stack exchange,提问作者soundarrajan v
相关产品推荐
相关产品推荐

