Python将32位指针地址传递给C函数

我想在Python脚本的共享库中调用我的C函数。 传递指针时出现问题,64位地址似乎被截断为被调用函数内的32位地址。 Python和我的库都是64位。

下面的示例代码演示了该问题。 Python脚本打印传递给C函数的数据的地址。 然后,从被调用的C函数内打印接收的地址。 此外,C函数通过打印本地创建内存的大小和地址来certificate它是64位。 如果以任何其他方式使用指针,则结果是段错误。

的CMakeLists.txt

cmake_minimum_required (VERSION 2.6) add_library(plate MODULE plate.c) 

plate.c

 #include  #include  void plate(float *in, float *out, int cnt) { void *ptr = malloc(1024); fprintf(stderr, "passed address: %p\n", in); fprintf(stderr, "local pointer size: %lu\n local pointer address: %p\n", sizeof(void *), ptr); free(ptr); } 

test_plate.py

 import numpy import scipy import ctypes N = 3 x = numpy.ones(N, dtype=numpy.float32) y = numpy.ones(N, dtype=numpy.float32) plate = ctypes.cdll.LoadLibrary('libplate.so') print 'passing address: %0x' % x.ctypes.data plate.plate(x.ctypes.data, y.ctypes.data, ctypes.c_int(N)) 

python-2.7的输出

在[1]中:运行../test_plate.py

传递地址:7f9a09b02320

传递地址:0x9b02320

本地指针大小:8

本地指针地址:0x7f9a0949a400

问题是ctypes模块不检查您尝试调用的函数的函数签名。 相反,它将C类型基于Python类型,所以行……

 plate.plate(x.ctypes.data, y.ctypes.data, ctypes.c_int(N)) 

……将前两个参数作为整数传递。 请参阅eryksun的答案,了解它们被截断为32位的原因。

为了避免截断,你需要告诉ctypes这些参数实际上是指针…

 plate.plate(ctypes.c_void_p(x.ctypes.data), ctypes.c_void_p(y.ctypes.data), ctypes.c_int(N)) 

…虽然它们实际指向是另一个问题 – 它们可能不是你的C代码所假设的float指针。


更新

从那以后, eryksun就这个问题中的numpy特定示例发布了一个更完整的答案,但是我将这里放在这里,因为它可能对于使用除numpy以外的程序员的指针截断的一般情况有用。

Python的PyIntObject在内部使用C long ,在大多数64位平台(64位Windows除外)上使用64位。 但是,ctypes将转换后的结果分配给pa->value.i ,其中value是union, i字段是32位int 。 有关详细信息,请参阅Modules / _ctypes / ConvParam中的ConvParam ,第588-607行和第645-664行。 ctypes是在Windows上开发的,其中long总是32位,但我不知道为什么没有更改为使用long字段,即pa->value.l 。 也许,大多数时候默认创建一个C int而不是使用long的全部范围会更方便。

无论如何,这意味着您不能简单地传递Python int来创建64位指针。 您必须显式创建一个ctypes指针。 你有很多选择。 如果您不关心类型安全性,NumPy数组的最简单选项是使用其ctypes属性。 这定义了钩子_as_parameter_ ,它允许Python对象设置它们在ctypes函数调用中的转换方式(参见前一个链接中的第707-719行)。 在这种情况下,它会创建一个void * 。 例如,你打电话给这样的plate

 plate.plate(x.ctypes, y.ctypes, N) 

但是,这不提供任何类型安全性来防止使用错误类型的数组调用函数,这将导致无意义,错误或分段错误。 np.ctypeslib.ndpointer解决了这个问题。 这将创建一个自定义类型,您可以在设置ctypes函数指针的argtypesrestype时使用该类型。 此类型可以validation数组的数据类型,维数,形状和标志。 例如:

 import numpy as np import ctypes c_npfloat32_1 = np.ctypeslib.ndpointer( dtype=np.float32, ndim=1, flags=['C', 'W']) plate = ctypes.CDLL('libplate.so') plate.plate.argtypes = [ c_npfloat32_1, c_npfloat32_1, ctypes.c_int, ] N = 3 x = np.ones(N, dtype=np.float32) y = np.ones(N, dtype=np.float32) plate.plate(x, y, N) # the parameter is the array itself 

如果你不告诉ctypes参数是什么类型,它会尝试从传递给函数的值推断它。 而这种推断并不总是能够满足您的需求。

处理此问题的推荐方法是设置函数的argtypes属性,以便明确告诉ctypes参数类型是什么。

 plate.plate.argtypes = [ ctypes.POINTER(ctypes.c_float), ctypes.POINTER(ctypes.c_float), ctypes.c_int ] 

然后你可以像这样调用函数:

 plate.plate(x.ctypes.data, y.ctypes.data, N) 

实际上,您应该设置plate.argstype = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int] ,然后可以接受来自python的c函数中的地址。
我遇到了问题,我就像我说的那样解决了问题。