如何使用ctypes封装返回指向malloc分配的数组的C函数?

6

我有一个C函数,用于读取二进制文件并返回一个动态大小的无符号整数数组(大小基于二进制文件中的元数据):

//example.c
#include <stdio.h>
#include <stdlib.h>

__declspec(dllexport)unsigned int *read_data(char *filename, size_t* array_size){
  FILE *f = fopen(filename, "rb");
  fread(array_size, sizeof(size_t), 1, f);
  unsigned int *array = (unsigned int *)malloc(*array_size * sizeof(unsigned int));
  fread(array, sizeof(unsigned int), *array_size, f);
  fclose(f);

  return array;
}

这个回答似乎表明了将从C创建的数组传递给Python的正确方式类似于这样:
# example_wrap.py
from ctypes import *
import os

os.add_dll_directory(os.getcwd())
indexer_dll = CDLL("example.dll")

def read_data(filename):
    filename = bytes(filename, 'utf-8')
    size = c_size_t()
    ptr = indexer_dll.read_data(filename, byref(size))
    return ptr[:size]

然而,当我运行Python包装器时,在ptr[:size]处代码悄无声息地失败了,就好像我试图访问一个越界的数组一样,而且很可能是这样,但是正确的传递动态大小数组的方法是什么?


你在Python代码中调用的indexer_dll.read_index()的定义在哪里? - Craig
在发布的C代码中。哦,抱歉,我正在重命名和缩减我的代码,以使其更通用,我忘记更改那个名称了。 - CSStudent7782
1个回答

2

需要考虑以下几点:

首先,您需要正确设置C函数的原型,以便ctypes可以正确地在C和Python类型之间转换。

其次,由于size实际上是一个ctypes.c_size_t对象,因此您实际上需要使用size.value来访问数组大小的数值。

第三,由于ptr[:size.value]实际上将数组内容复制到Python列表中,因此您需要确保还free()了已分配的C数组,因为您不会再使用它。

(也许将数组复制到Python列表并不理想,但我假设在这里这样做是可以的,否则在处理Python中的C数组时会更加复杂。)

这应该可以正常工作:

from ctypes import *
import os

os.add_dll_directory(os.getcwd())
indexer_dll = CDLL("example.dll")
indexer_dll.read_data.argtypes = [c_char_p, POINTER(c_size_t)
indexer_dll.read_data.restype = POINTER(c_int)
libc = cdll.msvcrt

def read_data(filename):
    filename = bytes(filename, 'utf-8')
    size = c_size_t()
    ptr = indexer_dll.read_data(filename, byref(size))
    result = ptr[:size.value]
    libc.free(ptr)
    return result

你解决了我提出的问题,谢谢。它在 libc.free(ptr) 处默默失败了。 - CSStudent7782
可能是msvcrt在使用不同的库。我通过创建一个free_mem C函数并通过dll调用它来修复了这个问题。 - CSStudent7782
@CSStudent7782 是的,我在Linux上测试过了(你实际上加载libc.so),在Windows上可能会有所不同...导出自己的函数肯定会起作用! - filbranden

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接