Python 3 ctypes 调用需要通过另一个结构间接引用缓冲区的函数

Python 3 ctypes call to a function that needs an indirect reference to a buffer through another structure

我有一个 C 共享库,其中包含一个接受一个参数的函数。 此参数是指向具有两个字段的结构的指针。

    typedef struct
    {   
        uint8_t     *p_data; // Pointer to a fixed lenth buffer (100 bytes)
        uint16_t     len;    // number of valid bytes in the buffer (range 1-100)
    } data_t;

我需要在我的 Python 3 脚本中设置一个 100 字节的缓冲区(我使用的是 3.7.2 / 3.7.3), 加载库并调用此函数。

    int 
    fn_convert_buffer(data_t *data_p)
    {
        ...
    }

我的 Python 3 次 ctypes 调用尝试命中了不兼容的类型。

    import ctypes as ct
    # load the library, etc...
    # lib_cdll = ct.CDLL(mySharedLib)

    def c_py_fn_convert_buffer(b_p):
            global lib_cdll
            val = lib_cdll.fn_convert_buffer(ct.byref(b_p))
            return int(val)

    data_a = bytearray(100)
    # Initialize the buffer with data.

    uint8_p = ct.c_uint8 * len(data_a)
    class BufferStruct_t (ct.Structure):
            _pack_ = 1
            _fields_ = [
                    ("p_data", ct.POINTER(ct.c_uint8 * len(data_a))),
                    ("len",    ct.c_uint16)
                    ]

    data_buf = BufferStruct_t(uint8_p.from_buffer(data_a), ct.c_uint16(8))
    # TypeError: incompatible types, c_ubyte_Array_100 instance 
    #            instead of LP_c_ubyte_Array_100 instance

    # Call C function in shared-library: int fn_convert_buffer(data_t *data_p);
    z = c_py_fn_convert_buffer(data_buf)

我需要帮助来理解我在上面的 BufferStruct_t 定义中遗漏的内容。 from_buffer 应该得到一个指向缓冲区的指针,但它似乎得到 c_ubyte_ARRAY_100.

上面的byref()也不行

    data_buf = BufferStruct_t(ct.byref(uint8_p.from_buffer(data_a)), ct.c_uint16(8))
    # TypeError: expected LP_c_ubyte_Array_100 instance, got CArgObject

为了测试我的流程的基础知识,我制作了一个示例案例,它将单独发送缓冲区和长度参数。

    def c_py_fn_convert_data(d_p,l):
            global lib_cdll
            val = lib_cdll.fn_convert_data(ct.byref(d_p),ct.c_uint32(l))
            return int(val)

    test_a = ct.c_uint8 * len(data_a)
    # Call C function in shared-library: 
    #   int fn_convert_data(uint8_t *data_p, uint32_t length); 
    z = c_py_fn_convert_data(test_a.from_buffer(data_a), 8)

这个简化案例有效。

如何构建一个 Python 3 对象,该对象携带对共享库函数期望的缓冲区的引用?


更新两个有效的变体。
更新 1 尝试了一个 cast 基于我后来读到的东西(我不会轻易投射 :-)
已更改,

    data_buf = BufferStruct_t(uint8_p.from_buffer(data_a), ct.c_uint16(8))

指向一个指向特定长度数组的指针,

    data_buf = BufferStruct_t(cast(uint8_p.from_buffer(data_a),
                                   ct.POINTER(ct.c_uint8 * len(data_a))),
                              ct.c_uint16(8))

根据 Mark 的回答更新 2。 从

更改为 _field_
                    ("p_data", ct.POINTER(ct.c_uint8 * len(data_a))),

到一个简单的指针形式,

                    ("p_data", ct.POINTER(ct.c_uint8)),

两种变体都有效。
不过,我很想知道这两种方式中哪一种更能处理 safe/correct ctypes。

  1. 转换成数组形式更好吗?或者,
  2. 使用简单的指针并依赖独立发送的长度是否更好?

您的结构定义声明了一个指向数组的指针,而不是 C 结构中的简单指针。这是一个简单的 DLL 实现示例,其中函数对数据求和:

test.c

#include <stdint.h>

#ifdef _WIN32
#   define API __declspec(dllexport)
#else
#   define API
#endif

typedef struct {
    uint8_t     *p_data;
    uint16_t     len;
} data_t;

API int fn_convert_buffer(data_t *data_p)
{
    int i;
    int sum = 0;
    for(i = 0; i < data_p->len; ++i)
        sum += data_p->p_data[i];
    return sum;
}

test.py

import ctypes as ct

class BufferStruct_t(ct.Structure):

    _pack_ = 1
    _fields_ = [("p_data", ct.POINTER(ct.c_uint8)), # just a pointer                    
                ("len",    ct.c_uint16)]

    # Helper to initialize the data
    def __init__(self,data):
        self.p_data = (ct.c_uint8 * len(data))(*data)
        self.len = len(data)

dll = ct.CDLL('test')
dll.fn_convert_buffer.argtypes = ct.POINTER(BufferStruct_t),
dll.fn_convert_buffer.restype = ct.c_int

data_buf = BufferStruct_t([1,2,3,4,5])
print(dll.fn_convert_buffer(data_buf))

输出:

15