如何将NumPy数组与ctypes一起使用?

19 投票
2 回答
35946 浏览
提问于 2025-04-16 00:56

我正在为我的C代码写一个Python接口,使用的是ctypes库。今天,我用一个别人用NumPy编写的Python版本替换了我的文件读取函数。之前的C版本是通过byref(p_data)来调用的,而p_data=PFloat()(见下面的代码)。主函数接收p_data

旧的文件读取代码:

p_data=POINTER(c_float)
foo.read(filename,byref(p_data))
result=foo.pymain(p_data)

而Python的文件读取函数返回的是一个NumPy数组。现在我有个问题:

我该如何把一个NumPy数组转换成POINTER(c_float)呢?

我在网上查了一下,但只找到了反向的内容:通过ctypes访问的C数组作为NumPy数组,还有一些我看不懂的东西:C-Types外部函数接口(numpy.ctypeslib)

[更新] 修正了示例代码中的一个错误

2 个回答

13

使用 np.ndarrays 作为 ctypes 参数

推荐的做法是使用 ndpointer,正如在 numpy文档中提到的。

这种方法比使用例如 POINTER(c_double) 更灵活,因为可以指定多个限制条件,这些条件在调用 ctypes 函数时会被验证。这些限制包括数据类型、维度数量、形状和标志。如果某个数组不满足指定的限制,就会抛出一个 TypeError 错误。

最小可复现示例

从 Python 调用 memcpy。最终需要调整标准 C 库 libc.so.6 的文件名。

import ctypes
import numpy as np

n_bytes_f64 = 8
nrows = 2
ncols = 5

clib = ctypes.cdll.LoadLibrary("libc.so.6")

clib.memcpy.argtypes = [
    np.ctypeslib.ndpointer(dtype=np.float64, ndim=2, flags='C_CONTIGUOUS'),
    np.ctypeslib.ndpointer(dtype=np.float64, ndim=1, flags='C_CONTIGUOUS'),
    ctypes.c_size_t]
clib.memcpy.restype = ctypes.c_void_p

arr_from = np.arange(nrows * ncols).astype(np.float64)
arr_to = np.empty(shape=(nrows, ncols), dtype=np.float64)

print('arr_from:', arr_from)
print('arr_to:', arr_to)

print('\ncalling clib.memcpy ...\n')
clib.memcpy(arr_to, arr_from, nrows * ncols * n_bytes_f64)

print('arr_from:', arr_from)
print('arr_to:', arr_to)

输出

arr_from: [0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
arr_to: [[0.0e+000 4.9e-324 9.9e-324 1.5e-323 2.0e-323]
 [2.5e-323 3.0e-323 3.5e-323 4.0e-323 4.4e-323]]

calling clib.memcpy ...

arr_from: [0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
arr_to: [[0. 1. 2. 3. 4.]
 [5. 6. 7. 8. 9.]]

如果你把 ndpointerndim=1/2 参数修改为与 arr_from/arr_to 的维度不一致,代码会因为 ArgumentError 而失败。

由于这个问题的标题比较笼统,...

ctypes.c_void_p 结果构建 np.ndarray

最小可复现示例

在下面的例子中,使用 malloc 分配了一些内存,并用 memset 填充了 0。然后构建一个 numpy 数组来访问这块内存。当然会出现一些所有权问题,因为 Python 不会释放在 C 中分配的内存。为了避免内存泄漏,必须通过 ctypes 再次 释放 分配的内存。可以使用 copy 方法让 np.ndarray 获得所有权

import ctypes
import numpy as np

n_bytes_int = 4
size = 7

clib = ctypes.cdll.LoadLibrary("libc.so.6")

clib.malloc.argtypes = [ctypes.c_size_t]
clib.malloc.restype = ctypes.c_void_p

clib.memset.argtypes = [
    ctypes.c_void_p,
    ctypes.c_int,
    ctypes.c_size_t]
clib.memset.restype = np.ctypeslib.ndpointer(
    dtype=np.int32, ndim=1, flags='C_CONTIGUOUS')

clib.free.argtypes = [ctypes.c_void_p]
clib.free.restype = ctypes.c_void_p


pntr = clib.malloc(size * n_bytes_int)
ndpntr = clib.memset(pntr, 0, size * n_bytes_int)
print(type(ndpntr))
ctypes_pntr = ctypes.cast(ndpntr, ctypes.POINTER(ctypes.c_int))
print(type(ctypes_pntr))
print()
arr_noowner = np.ctypeslib.as_array(ctypes_pntr, shape=(size,))
arr_owner = np.ctypeslib.as_array(ctypes_pntr, shape=(size,)).copy()
# arr_owner = arr_noowner.copy()


print('arr_noowner (at {:}): {:}'.format(arr_noowner.ctypes.data, arr_noowner))
print('arr_owner (at {:}): {:}'.format(arr_owner.ctypes.data, arr_owner))

print('\nfree allocated memory again ...\n')
_ = clib.free(pntr)

print('arr_noowner (at {:}): {:}'.format(arr_noowner.ctypes.data, arr_noowner))
print('arr_owner (at {:}): {:}'.format(arr_owner.ctypes.data, arr_owner))

print('\njust for fun: free some python-memory ...\n')
_ = clib.free(arr_owner.ctypes.data_as(ctypes.c_void_p))

print('arr_noowner (at {:}): {:}'.format(arr_noowner.ctypes.data, arr_noowner))
print('arr_owner (at {:}): {:}'.format(arr_owner.ctypes.data, arr_owner))

输出

<class 'numpy.ctypeslib.ndpointer_<i4_1d_C_CONTIGUOUS'>
<class '__main__.LP_c_int'>

arr_noowner (at 104719884831376): [0 0 0 0 0 0 0]
arr_owner (at 104719884827744): [0 0 0 0 0 0 0]

free allocated memory again ...

arr_noowner (at 104719884831376): [ -7687536     24381 -28516336     24381         0         0         0]
arr_owner (at 104719884827744): [0 0 0 0 0 0 0]

just for fun: free some python-memory ...

arr_noowner (at 104719884831376): [ -7687536     24381 -28516336     24381         0         0         0]
arr_owner (at 104719884827744): [ -7779696     24381 -28516336     24381         0         0         0]
37

你的代码看起来有点混乱——ctypes.POINTER() 创建的是一个新的 ctypes 指针 ,而不是 ctypes 实例。无论如何,将 NumPy 数组传递给 ctypes 代码最简单的方法是使用 numpy.ndarrayctypes 属性中的 data_as 方法。只要确保底层数据类型正确即可。例如:

import ctypes
import numpy
c_float_p = ctypes.POINTER(ctypes.c_float)
data = numpy.array([[0.1, 0.1], [0.2, 0.2], [0.3, 0.3]])
data = data.astype(numpy.float32)
data_p = data.ctypes.data_as(c_float_p)

撰写回答