9

我想在 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)

盘子.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

4

4 回答 4

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))

...尽管它们实际上指向是另一回事-它们可能不是float您的 C 代码所假定的指针。


更新

eryksun已经为这个问题中的 -specific 示例发布了一个更完整的答案numpy,但我将把它留在这里,因为对于使用numpy.

于 2013-05-19T13:57:27.907 回答
6

Python内部PyIntObject使用 C long,在大多数 64 位平台(不包括 64 位 Windows)上是 64 位。但是,ctypes 将转换后的结果分配给pa->value.i,其中value是一个并集,该i字段是一个 32 位的int。有关详细信息,请参阅Modules/_ctypes/ConvParam callproc.c中的第 588-607 和 645-664 行。ctypes 是在 Windows 上开发的,其中 a始终是 32 位,但我不知道为什么没有将其更改为使用该字段,即. 可能,大多数时候默认创建 C而不是使用.longlongpa->value.lintlong

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

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

但是,这并没有提供任何类型安全性来防止函数被错误类型的数组调用,这将导致废话、错误或分段错误。np.ctypeslib.ndpointer解决了这个问题。这将创建一个自定义类型,您可以在设置ctypes 函数指针的argtypesand时使用该类型。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
于 2013-05-20T12:26:37.443 回答
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)
于 2013-05-20T08:35:05.040 回答
1

实际上,你应该设置plate.argstype = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int],然后从python接受c func中的地址就可以了。
我遇到了这个问题,我按照我说的解决了它。

于 2017-09-11T08:26:11.250 回答