Python is passing 32bit pointer address to C functions

前端 未结 4 967
误落风尘
误落风尘 2021-02-15 04:04

I would like to call my C functions within a shared library from Python scripts. Problem arrises when passing pointers, the 64bit addresses seem to be truncated to 32bit address

相关标签:
4条回答
  • 2021-02-15 04:20

    The problem is that the ctypes module doesn't check the function signature of the function you're trying to call. Instead, it bases the C types on the Python types, so the line...

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

    ...is passing the the first two params as integers. See eryksun's answer for the reason why they're being truncated to 32 bits.

    To avoid the truncation, you'll need to tell ctypes that those params are actually pointers with something like...

    plate.plate(ctypes.c_void_p(x.ctypes.data),
                ctypes.c_void_p(y.ctypes.data),
                ctypes.c_int(N))
    

    ...although what they're actually pointers to is another matter - they may not be pointers to float as your C code assumes.


    Update

    eryksun has since posted a much more complete answer for the numpy-specific example in this question, but I'll leave this here, since it might be useful in the general case of pointer truncation for programmers using something other than numpy.

    0 讨论(0)
  • 2021-02-15 04:29

    Python's PyIntObject uses a C long internally, which is 64-bit on most 64-bit platforms (excluding 64-bit Windows). However, ctypes assigns the converted result to pa->value.i, where value is a union and the i field is a 32-bit int. For the details, see ConvParam in Modules/_ctypes/callproc.c, lines 588-607 and 645-664. ctypes was developed on Windows, where a long is always 32-bit, but I don't know why this hasn't been changed to use the long field instead, i.e. pa->value.l. Probably, it's just more convenient most of the time to default to creating a C int instead of using the full range of the long.

    Anyway, this means you can't simply pass a Python int to create a 64-bit pointer. You have to explicitly create a ctypes pointer. You have a number of options for this. If you're not concerned about type safety, the simplest option for a NumPy array is to use its ctypes attribute. This defines the hook _as_parameter_ that lets Python objects set how they're converted in ctypes function calls (see lines 707-719 in the previous link). In this case it creates a void *. For example, you'd call plate like this:

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

    However, this doesn't offer any type safety to prevent the function from being called with an array of the wrong type, which will result in either nonsense, bugs, or a segmentation fault. np.ctypeslib.ndpointer solves this problem. This creates a custom type that you can use in setting the argtypes and restype of a ctypes function pointer. This type can verify the array's data type, number of dimensions, shape, and flags. For example:

    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
    
    0 讨论(0)
  • 2021-02-15 04:40

    If you don't tell ctypes what type the parameters are, it attempts to infer it from the values that you pass to the function. And this inference will not always work as you need.

    The recommended way to deal with this is to set the argtypes attribute of the function and so explicitly tell ctypes what the parameter types are.

    plate.plate.argtypes = [
        ctypes.POINTER(ctypes.c_float), 
        ctypes.POINTER(ctypes.c_float), 
        ctypes.c_int
    ]
    

    Then you can call the function like this:

    plate.plate(x.ctypes.data, y.ctypes.data, N)
    
    0 讨论(0)
  • 2021-02-15 04:43

    Actually, You should set plate.argstype = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int], and then it will be ok to accept the address in c func from python.
    I met the problem and I solved it as what I say.

    0 讨论(0)
提交回复
热议问题