Python正在将32位指针地址传递给C函数
我想从Python脚本调用共享库中的C函数。问题出现在传递指针时,被调用函数中的64位地址似乎被截断为32位地址。Python和我的库都是64位的 下面的示例代码演示了该问题。Python脚本打印传递给C函数的数据的地址。然后,从被调用的C函数中打印接收到的地址。此外,C函数通过打印本地创建内存的大小和地址来证明它是64位的。如果指针以任何其他方式使用,则结果是segfault CMakeLists.txtPython正在将32位指针地址传递给C函数,python,c,macos,cmake,ctypes,Python,C,Macos,Cmake,Ctypes,我想从Python脚本调用共享库中的C函数。问题出现在传递指针时,被调用函数中的64位地址似乎被截断为32位地址。Python和我的库都是64位的 下面的示例代码演示了该问题。Python脚本打印传递给C函数的数据的地址。然后,从被调用的C函数中打印接收到的地址。此外,C函数通过打印本地创建内存的大小和地址来证明它是64位的。如果指针以任何其他方式使用,则结果是segfault CMakeLists.txt cmake_minimum_required (VERSION 2.6) add_li
cmake_minimum_required (VERSION 2.6)
add_library(plate MODULE plate.c)
板.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);
}
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))
…正在将前两个参数作为整数传递。有关它们被截断为32位的原因,请参见eryksun的答案
为了避免截断,您需要告诉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
以外的东西的程序员来说,如果您不告诉ctypes参数是什么类型的,它在指针截断的一般情况下可能会很有用,它试图从传递给函数的值中推断它。这个推论并不总是像你所需要的那样有效
处理此问题的推荐方法是设置函数的属性,并明确告知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.plate(x.ctypes, y.ctypes, N)
Python的
PyIntObject
在内部使用Clong
,在大多数64位平台(不包括64位窗口)上为64位。但是,ctypes将转换后的结果分配给pa->value.i
,其中value
是一个并集,i
字段是一个32位int
。有关详细信息,请参见第588-607行和第645-664行中的ConvParam
。ctypes是在Windows上开发的,long
始终是32位的,但我不知道为什么它没有改为使用long
字段,即pa->value.l
。也许,在大多数情况下,默认创建一个Cint
,而不是使用整个long
,会更方便
无论如何,这意味着您不能简单地传递Pythonint
来创建64位指针。必须显式创建ctypes指针。你有很多选择。如果您不关心类型安全,NumPy数组最简单的选项是使用其ctypes
属性。这将钩子\u定义为\u参数
,它允许Python对象设置如何在ctypes函数调用中转换它们(请参见前面链接中的第707-719行)。在这种情况下,它会创建一个void*
。例如,您可以这样调用plate
:
plate.plate(x.ctypes.data, y.ctypes.data, N)
plate.plate(x.ctypes, y.ctypes, N)
但是,这并不提供任何类型安全性来防止使用错误类型的数组调用函数,这将导致无意义、错误或分段错误。解决了这个问题。这将创建一个自定义类型,可用于设置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
实际上,您应该设置
plate.argstype=[ctypes.c\u void\u p,ctypes.c\u void\u p,ctypes.c\u int]
,然后就可以从python接受c func中的地址了。我遇到了这个问题,我照我说的解决了。@eryksun是一个Windows
long
在64位Windows上仍然只有32位吗?在64位Windows上,along
是32位的,along
是64位的。@eryksun很奇怪,但我想它的优点是一致的。谢谢!您的建议修复了指针地址截断问题。虽然正如您所暗示的,指针并不指向预期的浮点数据。我发现,如果使用dtype=numpy.float32
@eryksun创建数组,则示例将起作用。我个人认为,这些信息值得包含在回答中。我认为argtypes
是一个比目前公认的答案更好的解决方案。但很明显,我对细节了解不够,尤其是对于numpy
,这显然有点特别。你能补充一个答案吗。然后我会删除这个,并更新你的准确答案。我有一个类似的问题,但与numpy无关。在我的例子中,所有的函数原型都被定义了,但它没有帮助。我的64位指针仍被截断为32位:-(好吧,我的错:在我的例子中,我找到了指针被截断的原因。接收指针的函数的原型是可以的,但我遇到了另一个从ctype调用的C函数分配该指针的问题。该函数的返回类型必须使用restype=C\u void\u p
或oter指针或返回类型默认值来定义int。在我的cas中,我确实提供了重新键入,但我添加了一个输入错误(在重新键入后添加了一个尾随的“s”),它被忽略了。令人恼火的是,只要库只使用内存不足的地址,它有时可能会被忽略。