如何在Python中让DLL的C char**指针指向Python字符串?
问题:在Python回调中修改C语言的char**类型指针
需求是在Python的ctypes回调函数中,让C传入的char** results指向Python创建的字符串"321",但现有代码无法修改该变量。
原代码如下:
# py_parse_pdl_func是从已加载到Python程序中的C语言DLL调用的回调函数 # 以下是该回调函数在C语言中的声明 # typedef int (*tsl_pdl_cb_t)(void *pz_prv, const char **results, const char* query); # 尝试将results指向字符串"321" def py_parse_pdl_func(pz_prv, py_results, query): global product_class_void product_class = (ctypes.c_char_p)(b"321") product_class_void = ctypes.cast(product_class, ctypes.c_void_p) py_results.contents = ctypes.c_long(product_class_void.value) return 1
问题分析
- 参数类型未正确声明:注册回调时没明确指定
py_results的类型为POINTER(c_char_p),ctypes会默认将其当作c_void_p处理,导致指针操作无效。 - 类型赋值错误:直接给
py_results.contents赋值c_long类型,和char**对应的POINTER(c_char_p)类型不匹配,无法正确修改C侧指针。 - GC回收风险:临时创建的
product_class可能被Python垃圾回收器回收,导致C侧后续访问results时出现野指针。
正确解决方案
方案1:让C指向Python字符串(需确保内存不被回收)
通过全局变量保存字符串避免被GC回收,同时正确声明回调参数类型:
# 全局变量保存字符串,防止被GC回收 global_product_class = None def py_parse_pdl_func(pz_prv, py_results, query): global global_product_class # 创建字节串并转为c_char_p类型 global_product_class = ctypes.c_char_p(b"321") # 直接给POINTER(c_char_p)的contents赋值c_char_p对象 py_results.contents = global_product_class return 1 # 注册回调时必须明确参数类型 dll = ctypes.CDLL("你的DLL文件名.dll") # 定义回调类型,匹配C侧的声明 TSL_PDL_CB = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.POINTER(ctypes.c_char_p), ctypes.c_char_p) # 封装回调函数 py_callback = TSL_PDL_CB(py_parse_pdl_func) # 将回调传给DLL的对应函数(示例:假设DLL有set_callback函数) dll.set_callback(py_callback)
方案2:复制字符串到C堆内存(更安全,无GC风险)
如果C侧需要长期持有字符串,建议在C堆上分配内存并复制内容,避免依赖Python的GC:
def py_parse_pdl_func(pz_prv, py_results, query): str_content = b"321" # 在C堆上分配内存,长度为字符串长度+1(存储终止符) c_str = ctypes.c_char_p(ctypes.malloc(len(str_content) + 1)) # 复制字符串内容到C内存 ctypes.memmove(c_str, str_content, len(str_content)) # 添加C字符串必需的终止符 c_str[len(str_content)] = b'\x00' # 给results赋值 py_results.contents = c_str return 1 # 同样要正确注册回调类型 dll = ctypes.CDLL("你的DLL文件名.dll") TSL_PDL_CB = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.POINTER(ctypes.c_char_p), ctypes.c_char_p) py_callback = TSL_PDL_CB(py_parse_pdl_func) dll.set_callback(py_callback)
注意:方案2中分配的C堆内存,需要由C侧负责释放,或者在Python侧不再使用时调用ctypes.free(c_str)避免内存泄漏。
关键注意点
- 回调函数的参数类型必须严格匹配C侧的声明,否则会导致内存操作错误或程序崩溃。
- Python字符串的内存由GC管理,直接让C指向时必须确保字符串在C使用期间不会被回收。
- C语言字符串必须以
\x00结尾,否则会出现乱码或越界访问问题。
内容的提问来源于stack exchange,提问作者drlolly
相关产品推荐
相关产品推荐

