我想在Python脚本的共享库中调用我的C函数。传递指针时出现问题,64位地址似乎被截断为被调用函数内的32位地址。 Python和我的库都是64位。
下面的示例代码演示了此问题。 Python脚本打印传递给C函数的数据的地址。然后,从被调用的C函数内打印接收的地址。此外,C函数通过打印本地创建内存的大小和地址来证明它是64位。如果指针以任何其他方式使用,则结果是段错误。
的CMakeLists.txt
cmake_minimum_required (VERSION 2.6)
add_library(plate MODULE plate.c)
plate.c
#include <stdio.h>
#include <stdlib.h>
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
答案 0 :(得分:9)
问题是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
以外的其他东西的指针截断。
答案 1 :(得分:6)
Python的PyIntObject
在内部使用C long
,在大多数64位平台(64位Windows除外)上为64位。但是,ctypes会将转换后的结果分配给pa->value.i
,其中value
是联合,i
字段是32位int
。有关详细信息,请参阅Modules/_ctypes/callproc.c中的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函数指针的argtypes
和restype
时使用该类型。此类型可以验证数组的数据类型,维数,形状和标志。例如:
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
答案 2 :(得分:2)
如果你没有告诉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)
答案 3 :(得分:1)
实际上,您应该设置plate.argstype = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int]
,然后可以接受来自python的c函数中的地址。
我遇到了这个问题,就像我说的那样解决了它。