将numpy数组传递给Delphi DLL
我正在尝试在Python中实现一个图像分类算法。问题是,Python在处理数组时循环速度很慢。因此,我决定写一个Delphi的dll来处理数组。我的问题是,我不知道怎么把多维的Python数组传递给我的dll函数。
Delphi dll的代码片段:(我只用这个函数来测试)
type
TImgArray = array of array of Integer;
function count(a: TImgArray): Integer; cdecl;
begin
result:= high(a);
end;
相关的Python代码:
arraydll = cdll.LoadLibrary("C:\\ArrayFunctions.dll")
c_int_p = ctypes.POINTER(ctypes.c_int32)
data = valBD.ReadAsArray()
data = data.astype(np.int32)
data_p = data.ctypes.data_as(c_int_p)
print arraydll.count(data_p)
从dll函数返回的值不对(它是2816而不是7339)。所以我猜我的类型转换有问题 :( 感谢大家的帮助,
马里奥
2 个回答
普通的Python数组通常叫做“列表”。而numpy.array是一种特殊的类型,它比普通的Python列表在内存使用上更有效率。numpy.array把一块标准的内存封装起来,这块内存可以像C语言中的数组那样使用。不过,这种方式和Delphi的数组类型不太兼容。
正如David所说,如果你愿意使用C语言,这一切会简单很多。如果你想用Delphi来访问numpy.array,我觉得最简单的方法是找个办法导出一些简单的C函数来访问numpy.array。在C语言中,我会引入numpy的头文件,然后写一些可以从Pascal调用的函数。接着,我会从一个DLL中导入这些函数:
function GetNumpyArrayValue( arrayObj:Pointer; index:Integer):Double;
我已经有一段时间没写CPython的包装代码了。如果你只是想从Delphi访问核心的Python类型,这会简单一些。现有的Python-for-delphi的包装工具会对你有帮助。不过,使用numpy和Delphi结合起来会麻烦很多。
因为你只是写一个DLL,而不是整个应用程序,我建议你放弃Delphi,直接用C语言来写,这样更符合Python扩展的写法。
简单来说,由于你在Pascal中写一个DLL,你至少还需要另一个小的C语言DLL,来桥接Python扩展类型(numpy.array)和Python的浮点值之间的差异。即便如此,你也不容易(快速)得到一个可以在Delphi中作为本地数组类型读取的数组值。
我能想到的最快的访问方式是:
type
PDouble = ^Double;
function GetNumpyArrayValue( arrayObj:Pointer; var doubleVector:PDouble; var doubleVectorLen:Integer):Boolean;
你可以使用doubleVector(指针)类型的数学运算来访问底层的C数组内存。
你现在做的事情是行不通的,而且可能会导致内存损坏。
Delphi的动态数组在内部是用一种数据结构来实现的,这种结构会保存一些关于数组的信息,比如数组的长度。但是你传给它的是一种C语言风格的指针数组,这只是一个指针,而不是Delphi的动态数组数据结构。这个结构是特定于Delphi的,不能在其他语言中使用。(即使你在其他语言中成功实现了完全相同的结构,你也无法将其传递给Delphi的DLL并正常工作,因为Delphi的内存管理器在内部起着作用。这样做只会导致内存堆损坏和/或内存管理器抛出异常。)
如果你想把C风格的数组传入DLL,你必须按照C语言的方式来做,传递一个额外的参数来包含数组的长度。你的Python代码应该已经知道这个长度,而且计算它不会花费太多时间。你完全可以使用Delphi来加速图像处理。但数组的维度必须来自Python这边。这里没有捷径可走。
你的Delphi函数声明应该像这样:
type
TImgArray = array[0..MAXINT] of Integer;
PImgArray = ^TImgArray;
function ProcessSomething(a: PImgArray; size: integer): Integer; cdecl;