这是一个修改后的代码版本,在被调用的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代码:
from ctypes import c_uint32, c_float, c_size_t, c_void_p
from ctypes import Structure, POINTER, pointer, cast, cdll
from itertools import izip, islice
class _BNG_FFIParams(Structure):
_fields_ = [("source_ints", c_uint32),
("len", c_size_t),
("a", c_void_p),
("b", c_void_p)]
class _BNG_FFI(object):
int_type = c_uint32
float_type = c_float
_array_type = type(10 * int_type)
_lib = cdll.LoadLibrary('./testlib.so')
_converter = _lib.convert_to_bng
_converter.restype = c_void_p
_deallocate = _lib.free_bng_mem
_result_type = {int_type: float_type,
float_type: int_type}
def __init__(self):
my_params = _BNG_FFIParams()
self._params = my_params
self._pointer = POINTER(_BNG_FFIParams)(my_params)
def _getarray(self, seq, data_type):
if type(type(seq)) == self._array_type and seq._type_ is data_type:
print("Optimized!")
return seq
return (data_type * len(seq))(*seq)
def __call__(self, a, b, data_type=float_type):
length = len(a)
if length != len(b):
raise ValueError("Input lengths must be same")
a, b = (self._getarray(x, data_type) for x in (a, b))
result_type = POINTER(length * 2 * self._result_type[data_type])
params = self._params
params.source_ints = data_type is self.int_type
params.len = length
params.a = cast(pointer(a), c_void_p)
params.b = cast(pointer(b), c_void_p)
resptr = self._converter(self._pointer)
result = cast(resptr, result_type).contents
evens = islice(result, 0, None, 2)
odds = islice(result, 1, None, 2)
result = list(izip(evens, odds))
self._deallocate(resptr)
return result
convert = _BNG_FFI()
if __name__ == '__main__':
print(convert([1.0, 2.0, 3.0], [4.0, 5.0, 6.0], c_float))
print(convert([1, 2, 3], [4, 5, 6], c_uint32))
print(convert([1, 2, 3], (c_uint32 * 3)(4, 5, 6), c_uint32))
当我执行它时,这是结果:
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位的Ubuntu 14.04系统。我使用了Python 2.7,并使用gcc --shared ffitest.c -o testlib.so -Wall
来构建库。
BNG_FFITuple
作为FFI参数,还是仅在Python中使用?如果仅在Python中使用,建议使用collections.namedtuple。只需为int
和float
转换定义单独的errcheck
函数即可。你可以在BNG_FFIArray.__del__
中释放数组,但是使用指向lib.drop_array
的类引用BNG_FFIArray._drop_array
,以避免模块撤销在对象的__del__
终结器被调用之前将lib
设置为None
而出现问题。 - Eryk Sunbng_void_array_to_tuple_list
中,你将结果转换为BNG_FFITuple
数组。你是否曾经将BNG_FFITuple
传回给库?如果没有,那么使用ctypes结构体没有任何意义,可以将结果转换为普通的Pythontuple
或namedtuple
。一旦转换为BNG_FFIArray
,它是数组的唯一引用,因此可以使用其__del__
终结器调用drop_array
。 - Eryk Sunbng_void_array_to_tuple_list
之后不会再使用它。 - urschrei