如何用ctypes将C DLL的结构体二级指针内容读取到Python
解决Windows下Python 3.4用ctypes读取C DLL返回的结构体二级指针问题
遇到这种情况,大概率是二级指针的参数传递和类型映射没处理对。我来一步步给你拆解解决方案,先从C端的代码假设(按常见场景模拟),再对应Python端的实现:
第一步:明确C端的结构体和导出函数示例
假设你的C代码大概是这样的(如果和实际代码有差异,可对应调整):
#include <stdlib.h> #include <string.h> // 定义你的结构体 typedef struct { int id; char name[20]; float score; } Student; // 导出函数:通过二级指针返回结构体数组,同时用第二个参数返回数组长度 __declspec(dllexport) void GetStudentData(Student** pOutData, int* pOutCount) { // 模拟生成2条数据 *pOutCount = 2; // 分配内存 *pOutData = (Student*)malloc(*pOutCount * sizeof(Student)); // 填充第一条数据 (*pOutData)[0].id = 1001; strcpy((*pOutData)[0].name, "ZhangSan"); (*pOutData)[0].score = 92.5f; // 填充第二条数据 (*pOutData)[1].id = 1002; strcpy((*pOutData)[1].name, "LiSi"); (*pOutData)[1].score = 88.0f; } // 必须导出释放内存的函数!避免内存泄漏 __declspec(dllexport) void FreeStudentData(Student* pData) { free(pData); }
第二步:Python 3.4端的ctypes实现
重点是正确映射结构体类型、处理二级指针参数,代码如下:
import ctypes # 1. 定义对应C结构体的Python类,继承ctypes.Structure class Student(ctypes.Structure): _fields_ = [ ("id", ctypes.c_int), ("name", ctypes.c_char * 20), # 对应C的char[20] ("score", ctypes.c_float) ] # 2. 加载你的DLL(替换成你的DLL路径) dll = ctypes.WinDLL("YourDllName.dll") # 如果是cdecl调用约定,用ctypes.CDLL # 3. 设置GetStudentData函数的参数类型和返回类型 # 函数原型:void GetStudentData(Student**, int*) dll.GetStudentData.argtypes = [ ctypes.POINTER(ctypes.POINTER(Student)), # 对应C的Student** ctypes.POINTER(ctypes.c_int) # 对应C的int* ] dll.GetStudentData.restype = None # 返回值是void # 设置FreeStudentData函数的类型 dll.FreeStudentData.argtypes = [ctypes.POINTER(Student)] dll.FreeStudentData.restype = None # 4. 调用函数并获取数据 def get_student_data(): # 准备接收输出的参数 p_data = ctypes.POINTER(Student)() # 初始化一个空的指针,对应C的Student* count = ctypes.c_int(0) # 初始化长度变量 # 调用DLL函数:注意要传指针的地址(二级指针) dll.GetStudentData(ctypes.byref(p_data), ctypes.byref(count)) # 读取数据:把指针转换成结构体数组 student_list = [] for i in range(count.value): # 通过索引访问结构体数组元素 student = p_data[i] student_list.append({ "id": student.id, "name": student.name.decode("gbk"), # Windows默认GBK编码,按需调整 "score": student.score }) # 必须释放内存!否则会造成泄漏 dll.FreeStudentData(p_data) return student_list, count.value # 测试调用 if __name__ == "__main__": students, total = get_student_data() print(f"总共获取到{total}条数据:") for s in students: print(s)
关键注意事项(可能是你之前失败的原因)
- 二级指针的传递:C函数需要修改
Student*的指向,所以Python端必须传递POINTER(POINTER(Student))类型,用ctypes.byref(p_data)来传递指针的地址,而不是直接传p_data。 - 结构体字段的严格对应:C结构体的字段类型、顺序必须和Python的
_fields_完全一致,比如char数组要用ctypes.c_char * 长度,不能出错。 - 调用约定:如果你的C函数用的是
cdecl(比如GCC编译的),必须用ctypes.CDLL加载;如果是stdcall(MSVC默认),用ctypes.WinDLL。调用约定不匹配会导致参数栈混乱,数据读取错误。 - 内存释放:C端malloc的内存,Python端无法直接释放,必须调用DLL提供的释放函数,否则会造成内存泄漏。
排查你之前的问题
如果还是拿不到pReturn的数据,你可以检查这几点:
- 有没有正确设置函数的
argtypes?如果不设置,ctypes会默认转换参数类型,可能导致指针传递错误。 - 结构体的字段定义是否和C端完全一致?比如字段顺序、类型长度(比如char数组的长度)。
- 是不是忘记用
ctypes.byref传递指针的地址?直接传指针变量的话,C函数无法修改它的指向。
内容的提问来源于stack exchange,提问作者sports
相关产品推荐
相关产品推荐

