如何用Python3的ctypes调用C函数获取数组(不修改C代码)
解决ctypes调用返回栈指针的C函数的段错误问题
首先,咱们得先理清问题根源:你调用的C函数f把栈上的局部数组tmpB指针返回了,函数返回后这块栈内存会被系统回收,所以后续访问这个指针必然触发段错误。而且你还不能修改原C代码,那咱们得找个绕开这个问题的办法。
先说说你现有Python代码里的明显错误,这也是导致段错误的一部分原因:
- 你给
pfun.argtypes设置的参数类型完全不对,原C函数f的参数是(int, int*, int**),对应的ctypes类型应该是(ct.c_int, ct.POINTER(ct.c_int), ct.POINTER(ct.POINTER(ct.c_int))) - 传递二级指针
b的时候,应该用ct.byref(b)而不是直接传b - 用numpy数组传递的时候,直接用
a.ctypes.data_as(ct.POINTER(ct.c_int))比自定义_np_ptr更安全
不过即使修正了这些参数问题,核心的栈指针问题还是存在,所以咱们需要更彻底的解决方案。
方案一:写个C包装函数(最稳妥,推荐)
既然不能改原函数,咱们可以写个轻量的包装函数,把原函数返回的栈上数据立刻复制到堆内存里,这样返回的指针就是安全的堆指针,Python可以正常访问,最后记得释放内存就行。
步骤1:编写包装函数
创建一个wrapper.c文件,内容如下:
#include <stdlib.h> #include <string.h> // 声明原函数的接口 extern int f(int lenA, int *a, int** b); // 包装函数:调用原函数后把栈数据复制到堆 int* f_wrapper(int lenA, int *a, int *out_n) { int *stack_ptr; *out_n = f(lenA, a, &stack_ptr); // 分配堆内存并复制数据 int *heap_ptr = malloc(*out_n * sizeof(int)); if (heap_ptr != NULL) { memcpy(heap_ptr, stack_ptr, *out_n * sizeof(int)); } return heap_ptr; } // 提供释放堆内存的函数,避免内存泄漏 void free_heap_data(int *ptr) { free(ptr); }
步骤2:编译成动态库
把原函数代码(test.c)和包装函数一起编译成动态库:
gcc -shared -fPIC wrapper.c test.c -o test_wrapper.so
步骤3:Python调用包装后的函数
现在写Python代码调用这个包装函数,就可以安全获取数据了:
import ctypes as ct import numpy as np def main(): # 加载包装后的动态库 lib = ct.CDLL('./test_wrapper.so') # 定义函数的参数和返回类型 lib.f_wrapper.argtypes = (ct.c_int, ct.POINTER(ct.c_int), ct.POINTER(ct.c_int)) lib.f_wrapper.restype = ct.POINTER(ct.c_int) lib.free_heap_data.argtypes = (ct.POINTER(ct.c_int),) lib.free_heap_data.restype = None # 准备输入数组 a = np.array([1, 2, 3, -7, -9, 100], dtype=np.int32) len_a = ct.c_int(a.size) n = ct.c_int(0) # 调用包装函数 b_heap = lib.f_wrapper(len_a, a.ctypes.data_as(ct.POINTER(ct.c_int)), ct.byref(n)) n_val = n.value # 提取数组数据 positive_numbers = [b_heap[i] for i in range(n_val)] print(f"正数个数: {n_val}") print(f"正数数组: {positive_numbers}") # 必须释放堆内存,避免泄漏 lib.free_heap_data(b_heap) if __name__ == "__main__": main()
这个方案的优点是完全不依赖系统和编译器的栈布局,非常稳定,而且只需要写几行C代码,完全不修改原函数。
方案二:纯Python黑科技(依赖栈布局,不推荐)
如果实在不想写任何C代码,咱们可以利用栈内存的时效性:在原函数返回前(栈内存还没被回收的时候)把数据复制出来。不过这个方法只在特定系统(比如Linux x86-64)和编译器下有效,稳定性差,仅供参考。
思路是:先调用一次函数得到正数个数n,然后再次调用函数,在函数返回的瞬间,通过栈的位置找到tmpB的数据并复制。不过这个方法需要对栈布局有一定了解,而且很容易因为编译器优化、函数调用栈变化而失效,所以还是推荐方案一。
内容的提问来源于stack exchange,提问作者kitsune_breeze
相关产品推荐
相关产品推荐

