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

pap*_*bla 8 c python macos ctypes cmake

我想在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)
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

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))
Run Code Online (Sandbox Code Playgroud)

python-2.7的输出

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

传递地址:7f9a09b02320

传递地址:0x9b02320

本地指针大小:8

本地指针地址:0x7f9a0949a400

Aya*_*Aya 9

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

plate.plate(x.ctypes.data, y.ctypes.data, ctypes.c_int(N))
Run Code Online (Sandbox Code Playgroud)

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

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

plate.plate(ctypes.c_void_p(x.ctypes.data),
            ctypes.c_void_p(y.ctypes.data),
            ctypes.c_int(N))
Run Code Online (Sandbox Code Playgroud)

...虽然它们实际指向是另一个问题 - 但它们可能不是float您的C代码所假设的指针.


更新

从那以后,eryksunnumpy这个问题的特定示例发布了一个更为完整的答案,但是我会把它留在这里,因为它可能对于程序员使用其他东西的指针截断的一般情况很有用numpy.

  • 在64位Windows上,"long"是32位,"long long"是64位. (2认同)

Ery*_*Sun 6

Python 内部PyIntObject使用C long,在大多数64位平台(64位Windows除外)上为64位.但是,ctypes将转换后的结果分配给pa->value.i,其中value是union,i字段是32位int.有关详细信息,请参阅ConvParam模块/ _ctypes/callproc.c,线588-607和645-664.ctypes是在Windows上开发的,其中a 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)
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)