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

Python正在将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

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

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

CMakeLists.txt

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
在内部使用C
long
,在大多数64位平台(不包括64位窗口)上为64位。但是,ctypes将转换后的结果分配给
pa->value.i
,其中
value
是一个并集,
i
字段是一个32位
int
。有关详细信息,请参见第588-607行和第645-664行中的
ConvParam
。ctypes是在Windows上开发的,
long
始终是32位的,但我不知道为什么它没有改为使用
long
字段,即
pa->value.l
。也许,在大多数情况下,默认创建一个C
int
,而不是使用整个
long
,会更方便

无论如何,这意味着您不能简单地传递Python
int
来创建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上,a
long
是32位的,a
long
是64位的。@eryksun很奇怪,但我想它的优点是一致的。谢谢!您的建议修复了指针地址截断问题。虽然正如您所暗示的,指针并不指向预期的浮点数据。我发现,如果使用
dtype=numpy.float32
@eryksun创建数组,则示例将起作用。我个人认为,这些信息值得包含在回答中。我认为
argtypes
是一个比目前公认的答案更好的解决方案。但很明显,我对细节了解不够,尤其是对于
numpy
,这显然有点特别。你能补充一个答案吗。然后我会删除这个,并更新你的准确答案。我有一个类似的问题,但与numpy无关。在我的例子中,所有的函数原型都被定义了,但它没有帮助。我的64位指针仍被截断为32位:-(好吧,我的错:在我的例子中,我找到了指针被截断的原因。接收指针的函数的原型是可以的,但我遇到了另一个从ctype调用的C函数分配该指针的问题。该函数的返回类型必须使用
restype=C\u void\u p
或oter指针或返回类型默认值来定义int。在我的cas中,我确实提供了重新键入,但我添加了一个输入错误(在重新键入后添加了一个尾随的“s”),它被忽略了。令人恼火的是,只要库只使用内存不足的地址,它有时可能会被忽略。