【问题标题】:Python is passing 32bit pointer address to C functionsPython 正在将 32 位指针地址传递给 C 函数
【发布时间】:2013-05-14 04:03:49
【问题描述】:

我想从 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

【问题讨论】:

    标签: python c macos cmake ctypes


    【解决方案1】:

    问题在于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 以外的其他东西的程序员的截断。

    【讨论】:

    • @eryksun Windows long 在 64 位 Windows 上仍然只有 32 位吗?
    • 在 64 位 Windows 上,long 是 32 位,long long 是 64 位。
    • @eryksun 很奇怪,但我想它具有保持一致的优点。
    • 谢谢!您的建议解决了指针地址截断问题。尽管正如您所暗示的那样,指针并不指向预期的浮点数据。
    • 我发现该示例将使用dtype=numpy.float32创建数组的工作
    【解决方案2】:

    Python 的 PyIntObject 在内部使用 C long,它在大多数 64 位平台(不包括 64 位 Windows)上是 64 位的。但是,ctypes 将转换后的结果分配给pa-&gt;value.i,其中value 是一个联合,i 字段是一个32 位int。有关详细信息,请参阅 Modules/_ctypes/callproc.c 中的 ConvParam,第 588-607 和 645-664 行。 ctypes 是在 Windows 上开发的,其中 long 始终是 32 位的,但我不知道为什么没有改为使用 long 字段,即 pa-&gt;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 函数指针的 argtypesrestype。这种类型可以验证数组的数据类型、维数、形状和标志。例如:

    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
    

    【讨论】:

      【解决方案3】:

      如果你不告诉 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)
      

      【讨论】:

      • @eryksun 我个人认为这些信息值得包含在答案中。我认为argtypes 是比当前接受的答案更好的解决方案。但显然我对细节的了解还不够,尤其是numpy,这显然有点特别。你能添加一个答案。然后我会删除这个并支持你的准确答案。
      • 我有类似的问题,但与 numpy 无关。在我的情况下,所有函数原型都已定义,但没有帮助。我的 64 位指针仍被截断为 32 位 :-(
      • 好吧,我的错:我发现了在我的情况下指针被截断的原因。接收指针的函数原型没问题,但是我在使用从 ctype 调用的另一个 C 函数分配该指针时遇到了麻烦。该函数的返回类型必须使用restype = c_void_p 或其他指针定义,或者返回类型默认为int。在我的案例中,我确实提供了 restype,但我添加了一个错字(restype 之后的尾随“s”)并且它被忽略了。烦人的部分是,只要库只使用低内存中的地址,它就可以在某个时候被忽视。
      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 2020-01-09
        • 2019-07-26
        • 1970-01-01
        • 2021-12-27
        • 1970-01-01
        • 1970-01-01
        • 2012-02-22
        • 2016-09-15
        • 1970-01-01
        相关资源
        最近更新 更多