构造ctypes类的简洁方法

2024-06-14 02:53:12 发布

您现在位置:Python中文网/ 问答频道 /正文

我定义了一个ctypes类和一个相关的便利函数,如下所示:

class BNG_FFITuple(Structure):
    _fields_ = [("a", c_uint32),
                ("b", c_uint32)]


class BNG_FFIArray(Structure):
    _fields_ = [("data", c_void_p),
                ("len", c_size_t)]

    # Allow implicit conversions from a sequence of 32-bit unsigned ints
    @classmethod
    def from_param(cls, seq):
        return seq if isinstance(seq, cls) else cls(seq)

    def __init__(self, seq, data_type = c_float):
        array_type = data_type * len(seq)
        raw_seq = array_type(*seq)
        self.data = cast(raw_seq, c_void_p)
        self.len = len(seq)


def bng_void_array_to_tuple_list(array, _func, _args):
    res = cast(array.data, POINTER(BNG_FFITuple * array.len))[0]
    return res

convert = lib.convert_to_bng
convert.argtypes = (BNG_FFIArray, BNG_FFIArray)
convert.restype = BNG_FFIArray
convert.errcheck = bng_void_array_to_tuple_list
drop_array = lib.drop_array 
drop_array.argtypes = (POINTER(BNG_FFIArray),)

然后定义一个简单的方便函数:

^{pr2}$

大多数方法都很完美,但我有两个问题:

  • 它不够灵活;我希望能够使用c_float而不是c_uint32(因此字段是c_float),反之亦然,所以BNG_FFIArraydata_type是{}。不过,我不清楚该怎么做。在
  • 我想释放Python现在拥有的内存,方法是发送一个POINTER(BNG_FFIArray)回我的dylib(参见drop_array-我已经在dylib中定义了一个函数),但是我不确定在什么时候应该调用它。在

有没有一种更简洁、更像Python的方式来封装所有这些,这样也更安全?我担心的是,如果没有以健壮的方式定义内存清理(on __exit____del__?)任何出错的事情都会导致记忆不清


Tags: 函数convertdatalen定义deftypearray
2条回答

由于您对rust端有一定的控制权,所以最简单的做法是在调用之前从Python预先分配结果数组,并在单个结构中传递所有内容。在

下面的代码假定进行了此修改,但也指定了如果无法执行此操作,则将执行释放操作的位置。在

请注意,如果您进行这种封装,则不需要为库函数指定参数和结果处理等内容,因为您只从单个位置调用实际函数,并且始终使用完全相同的参数类型。在

我不知道rust(甚至我的C也有点生锈),但下面的代码假设您重新定义了rust,以匹配类似的东西:

typedef struct FFIParams {
    int32 source_ints;
    int32 len;
    void * a;
    void * b;
    void * result;
} FFIParams;

void convert_to_bng(FFIParams *p) {
}

这是Python。最后要注意的是,由于参数结构的重用,这不是线程安全的。如果需要的话,这很容易修复。在

^{pr2}$

下面是在调用的DLL中分配返回数组的代码的修改版本。因为用纯Python进行测试比较困难,而且我不懂rust,所以我为实际测试构建了一个低俗的C库:

#include <stdlib.h>
#include <stdio.h>

typedef struct FFIParams {
    int source_ints;
    int len;
    void * a;
    void * b;
} FFIParams, *FFIParamsPtr;

typedef int * intptr;
typedef float * floatptr;

void * to_float(FFIParamsPtr p) {
    floatptr result;
    intptr a = p->a;
    intptr b = p->b;
    int i;
    int size = sizeof(result[0]) * 2 * p->len;
    result = malloc(size);
    printf("Allocated %x bytes at %x\n", size, (unsigned int)result);
    for (i = 0; i < p->len; i++) {
        result[i*2+0] = (float)(a[i]);
        result[i*2+1] = (float)(b[i]);
    }
    return result;
}

void * to_int(FFIParamsPtr p) {
    intptr result;
    floatptr a = p->a;
    floatptr b = p->b;
    int i;
    int size = sizeof(result[0]) * 2 * p->len;
    result = malloc(size);
    printf("Allocated %x bytes at %x\n", size, (unsigned int)result);
    for (i = 0; i < p->len; i++) {
        result[i*2+0] = (int)(a[i]);
        result[i*2+1] = (int)(b[i]);
    }
    return result;
}

void * convert_to_bng(FFIParamsPtr p) {
    if (p->source_ints)
        return to_float(p);
    return to_int(p);
}

void free_bng_mem(void * data) {
    printf("Deallocating memory at %x\n", (unsigned int)data);
    free(data);
}

下面是调用它的Python代码:

^{pr2}$

下面是我执行的结果:

Allocated 18 bytes at 9088468
Deallocating memory at 9088468
[(1L, 4L), (2L, 5L), (3L, 6L)]
Allocated 18 bytes at 908a6b8
Deallocating memory at 908a6b8
[(1.0, 4.0), (2.0, 5.0), (3.0, 6.0)]
Optimized!
Allocated 18 bytes at 90e1ae0
Deallocating memory at 90e1ae0
[(1.0, 4.0), (2.0, 5.0), (3.0, 6.0)]

这是一个32位的Ubuntu14.04系统。我使用Python2.7,用gcc shared ffitest.c -o testlib.so -Wall构建了这个库

相关问题 更多 >