如何使用ctypes将数组从C++函数返回给Python

29

我正在使用ctypes在Python中实现一个C++函数。该C++函数应返回一个指向数组的指针。不幸的是,我还没有弄清楚如何在Python中访问该数组。我尝试了numpy.frombuffer,但没有成功。它只返回了一些随意的数字数组。显然我没有正确地使用它。这里是一个包含大小为10的数组的简单示例:

function.cpp文件的内容:

extern "C" int* function(){
int* information = new int[10];
for(int k=0;k<10;k++){
    information[k] = k;
}
return information;
}

包装器(wrapper).py的内容:

import ctypes
import numpy as np

output = ctypes.CDLL('./library.so').function()

ArrayType = ctypes.c_double*10
array_pointer = ctypes.cast(output, ctypes.POINTER(ArrayType))
print np.frombuffer(array_pointer.contents)

我正在使用以下方法编译C++文件:

g++ -c -fPIC function.cpp -o function.o
g++ -shared -Wl,-soname,library.so -o library.so function.o

你有什么建议,我应该如何在Python中访问数组值?


当然,我忘记导入一些特定的ctypes函数,如c_double和POINTER。我只是忘记在这里添加它们。 - dolby
看看这个答案:https://dev59.com/MWXWa4cB1Zd3GeqPRN0C#74612547 - Devil
2个回答

28

进行一些小的修改后,您的Python代码将能够正常工作:

import ctypes

f = ctypes.CDLL('./library.so').function
f.restype = ctypes.POINTER(ctypes.c_int * 10)
print [i for i in f().contents] # output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

基本上有两个更改:

  1. 删除与numpy相关的代码和ctypes.cast调用,因为我们不需要它们。

  2. 指定返回类型为ctypes.POINTER(ctypes.c_int * 10)

    默认情况下,假定外部函数返回C int类型,因此我们需要将其更改为所需的指针类型。

顺便说一句,从C代码返回一个新数组到Python代码似乎不合适。谁会在何时释放内存?最好在Python代码中创建数组并将其传递给C代码。这样就清楚了,Python代码拥有这些数组,并负责创建和回收它们的空间。


12
您能指出如何在Python中创建这样一个数组并将其指针传递给C吗? - shahensha
@Shahensha 我对于 int/float 数组的处理是使用 numpy 数组,以及嵌入式的 ctypes 接口(data_as)。 - Adversus
这里有一点点黑魔法...完成后如何删除内存? - XapaJIaMnu
@XapaJIaMnu 你可以创建一个单独的函数,在你安全返回并在 Python 中复制数组后释放已分配的内存。 - VMMF
如果返回数组的大小未知怎么办?f.restype = ctypes.POINTER(ctypes.c_int * 什么?) - VMMF
看这个答案:https://dev59.com/MWXWa4cB1Zd3GeqPRN0C#74612547 - Devil

24

function.cpp返回一个int数组,而wrapper.py试图将它们解释为double。将ArrayType更改为ctypes.c_int * 10,然后它应该可以工作。


使用np.ctypeslib可能会更容易,而不是自己使用frombuffer。这应该看起来像以下代码:

import ctypes
from numpy.ctypeslib import ndpointer

lib = ctypes.CDLL('./library.so')
lib.function.restype = ndpointer(dtype=ctypes.c_int, shape=(10,))

res = lib.function()

我还导入了c_int并替换了c_double,但是Python现在只打印出一个仅有5个元素的数组,而且值仍然是错误的。这很奇怪。 - dolby
非常好,非常感谢!现在它已经起作用了。Python仅给出警告:“[...]RuntimeWarning:无效的PEP 3118格式字符串:'<P' [...]”,但根据此讨论,这并不是问题。 - dolby
2
这里有一点点黑魔法...完成后如何删除内存? - XapaJIaMnu
2
@XapaJlaMnu 你的库应该有一个相应的 extern C 函数来调用释放内存,它在内部使用 C++ 的 delete 如果是用 new 分配的。ctypes 使用 C 接口,由于 C 没有 delete,因此 ctypes 也没有。 - Danica
2
如其他答案所建议,如果可能的话,最好使用numpy分配内存并将其传递给C函数进行填充。 - Danica
显示剩余2条评论

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