Python与ctypes:传引用传递结构体时遇空指针访问错误
问题:调用返回结构体数组的DLL时出现“Null Pointer Access”错误
我写了一个会分配内存并填充结构体后返回给Python的DLL,但在Python调用时触发了“Null Pointer Access”错误。
C代码(getatt.c,编译为getatt.dll)
typedef struct _ATTO { char title[132 + 1]; char val[132 + 1]; } ATT; __declspec(dllexport) void get_file_attributes(char* psFileName, ATT** *StructAtt, int* iTotalAtt) { int j = 0; int iError = NONE; char cMessage[132 + 1] = ""; int iNumAtt = 5; StructAtt = (ATT***)malloc(iNumAtt * sizeof(ATT**)); if(StructAtt == NULL) { fprintf(stderr, "Memory allocation failed\n"); return; } if(iNumAtt > 0) { for(i = 0; i < iNumAtt; i++) { StructAtt[i] = malloc(sizeof(ATT*)); if(StructAtt[i] == NULL) { fprintf(stderr, "Memory allocation failed\n"); return ; } for(j = 0; j < 1; j++) { StructAtt[i][j] = (ATT*)malloc(sizeof(ATT)); if(StructAtt[i][j] == NULL) { fprintf(stderr, "Memory allocation failed\n"); return ; } strcpy((StructAtt[i][j])->title, psStructAttInfoTitle[i]); //psStructAttInfoTitle是标题数组 strcpy((StructAtt[i][j])->val, psStructAttInfoVal[i]); //psStructAttInfoVal是值数组 sprintf(cMessage, "%s=%s\n", StructAtt[i][j]->title, StructAtt[i][j]->val); // write_output(cMessage); } } } // 打印值 for(i = 0; i < iNumAtt; i++) { for(j = 0; j < 1; j++) { sprintf(message, "%s=%s\n", (StructAtt)[i][j]->title, (StructAtt)[i][j]->val); write_output(message); } } *iTotalAtt = iNumAtt; return; }
DLL运行输出
TYPE=0 SUF=C GROUP=a OWNER=unknown NUMSUF=467
Python调用代码
import ctypes from ctypes import * class PY_STRUCT(Structure): _fields_ = [('title', c_char * 133), ('value', c_char * 133)] file_name = 'Forest23wd' file_att_dll = CDLL('./getatt') file_att_dll.get_file_attributes.argtypes = [c_char_p, POINTER(POINTER(POINTER(PY_STRUCT))),POINTER(c_int)] file_name_str = c_char_p(file_name.encode('utf-8')) attr_list = POINTER(POINTER(PY_STRUCT))() total_attr = c_int(0) file_att_dll.get_file_attributes(file_name_str, byref(attr_list), byref(total_attr)) # 访问数据 print(total_attr.value) print(attr_list[0][0].contents.title) # 触发错误:ValueError: NULL pointer access...
我能获取到total_attr.value为5,但访问attr_list[0][0]时触发空指针错误,预期这里应该返回“TYPE”。
错误原因分析
- 指针传递错误:C函数中
ATT*** StructAtt是按值传递的,你直接给StructAtt分配内存,修改的只是函数内部的局部变量,并没有把分配好的地址传递回Python,导致Python侧的attr_list始终是空指针。 - 冗余的指针层级:这里完全不需要三级指针(
ATT***),用二级指针(ATT**)就足够返回结构体数组,三级指针徒增复杂度。 - 字段名不匹配:Python结构体中定义的是
value,但C结构体中是val,会导致内存解析错误。
修正方案
1. 修正C代码
调整参数传递方式,通过二级指针返回结构体数组,同时简化内存分配逻辑,添加内存泄漏防护:
#include <stdio.h> #include <stdlib.h> #include <string.h> // 测试用的标题和值数组 const char* psStructAttInfoTitle[] = {"TYPE", "SUF", "GROUP", "OWNER", "NUMSUF"}; const char* psStructAttInfoVal[] = {"0", "C", "a", "unknown", "467"}; #define NONE 0 typedef struct _ATTO { char title[133]; char val[133]; } ATT; __declspec(dllexport) void get_file_attributes(char* psFileName, ATT** *pStructAtt, int* iTotalAtt) { int i; int iNumAtt = 5; // 给结构体数组指针分配内存,通过*pStructAtt传递回调用者 *pStructAtt = (ATT**)malloc(iNumAtt * sizeof(ATT*)); if(*pStructAtt == NULL) { fprintf(stderr, "Memory allocation failed\n"); *iTotalAtt = 0; return; } for(i = 0; i < iNumAtt; i++) { // 给单个结构体分配内存 (*pStructAtt)[i] = (ATT*)malloc(sizeof(ATT)); if((*pStructAtt)[i] == NULL) { fprintf(stderr, "Memory allocation failed\n"); // 释放已分配的内存,避免泄漏 for(int k = 0; k < i; k++) { free((*pStructAtt)[k]); } free(*pStructAtt); *pStructAtt = NULL; *iTotalAtt = 0; return ; } // 安全填充字符串,避免缓冲区溢出 strncpy((*pStructAtt)[i]->title, psStructAttInfoTitle[i], 132); (*pStructAtt)[i]->title[132] = '\0'; strncpy((*pStructAtt)[i]->val, psStructAttInfoVal[i], 132); (*pStructAtt)[i]->val[132] = '\0'; printf("%s=%s\n", (*pStructAtt)[i]->title, (*pStructAtt)[i]->val); } *iTotalAtt = iNumAtt; return; } // 导出内存释放函数,避免Python侧内存泄漏 __declspec(dllexport) void free_attributes(ATT** attrs, int count) { for(int i = 0; i < count; i++) { free(attrs[i]); } free(attrs); }
2. 修正Python代码
调整结构体字段名、指针类型,并添加内存释放逻辑:
import ctypes from ctypes import * class PY_STRUCT(Structure): _fields_ = [('title', c_char * 133), ('val', c_char * 133)] # 和C结构体字段名保持一致 file_name = 'Forest23wd' file_att_dll = CDLL('./getatt') # 调整参数类型,对应C的ATT** file_att_dll.get_file_attributes.argtypes = [c_char_p, POINTER(POINTER(PY_STRUCT)), POINTER(c_int)] file_att_dll.get_file_attributes.restype = None # 注册内存释放函数 file_att_dll.free_attributes.argtypes = [POINTER(PY_STRUCT), c_int] file_att_dll.free_attributes.restype = None file_name_str = file_name.encode('utf-8') attr_list = POINTER(PY_STRUCT)() # 对应C的ATT** total_attr = c_int(0) file_att_dll.get_file_attributes(file_name_str, byref(attr_list), byref(total_attr)) # 访问并打印数据 print(f"总属性数: {total_attr.value}") for i in range(total_attr.value): title = attr_list[i].title.decode('utf-8') val = attr_list[i].val.decode('utf-8') print(f"{title} = {val}") # 释放内存,避免泄漏 file_att_dll.free_attributes(attr_list, total_attr)
内容的提问来源于stack exchange,提问作者nas
相关产品推荐
相关产品推荐

