c_int python ctypes ffi

python - c_int()



Forma limpia de estructurar clase de ctypes. (2)

Aquí hay una versión modificada del código que asigna la matriz de retorno en la DLL llamada. Dado que eso sería más difícil de probar con Python puro, y como no conozco el óxido, construí una biblioteca de C para la prueba real:

#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); }

Aquí está el código de Python que lo llama:

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): # Optimization for pre-allocated correct array 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)) # This has the salutary side-effect of insuring we were # passed a valid type 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))

Y aquí está el resultado cuando lo ejecuté:

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)]

Esto pasa a ser un sistema Ubuntu 14.04 de 32 bits. Utilicé Python 2.7 y construí la biblioteca con gcc --shared ffitest.c -o testlib.so -Wall

He definido una clase de ctypes y una función de conveniencia asociada así:

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),)

Entonces defino una función de conveniencia simple:

def f(a, b): return [(i.a, i.b) for i in iter(convert(a, b))]

La mayoría de esto funciona perfectamente, pero tengo dos problemas:

  • No es lo suficientemente flexible; Me gustaría poder instanciar un BNG_FFITuple usando c_float lugar de c_uint32 (por lo que los campos son c_float ), y viceversa, por lo que el tipo de BNG_FFIArray data_type es c_uint32 . Aunque no tengo claro cómo hacer esto.
  • Me gustaría liberar la memoria que ahora es propiedad de Python, enviando un POINTER(BNG_FFIArray) nuevo a mi dylib (vea drop_array - ya definí una función en mi dylib), pero no estoy seguro de en qué Punto que debería llamarlo.

¿Hay una manera de encapsular todo esto de una manera más limpia, más pitón, que también es más segura? Me preocupa que, sin que la limpieza de la memoria se defina de manera robusta (en __exit__ ? __del__ ?), Cualquier cosa que no funcione correctamente llevará a una memoria no liberada.


Ya que tienes algo de control sobre el lado del óxido, lo más limpio sería realizar una asignación previa de la matriz de resultados de Python antes de la llamada, y pasar todo en una sola estructura.

El código siguiente asume esta modificación, pero también designa el lugar donde haría la desasignación si no puede hacerlo.

Tenga en cuenta que si realiza este tipo de encapsulación, NO necesita especificar cosas como los parámetros y el procesamiento de resultados para la función de biblioteca, ya que solo está llamando a la función real desde un solo lugar, y siempre con exactamente el mismo tipo de parámetros

No sé óxido (e incluso mi C está un poco oxidada), pero el código que aparece a continuación asume que redefine su óxido para que coincida con el equivalente de algo como esto:

typedef struct FFIParams { int32 source_ints; int32 len; void * a; void * b; void * result; } FFIParams; void convert_to_bng(FFIParams *p) { }

Aquí está el pitón. Una nota final: esto no es seguro para subprocesos, debido a la reutilización de la estructura del parámetro. Eso es bastante fácil de arreglar si es necesario.

from ctypes import c_uint32, c_float, c_size_t, c_void_p from ctypes import Structure, POINTER, pointer, cast from itertools import izip, islice _test_standalone = __name__ == ''__main__'' if _test_standalone: class lib(object): @staticmethod def convert_to_bng(ptr_params): params = ptr_params.contents source_ints = params.source_ints types = c_uint32, c_float if not source_ints: types = reversed(types) length = params.len src_type, dst_type = types src_type = POINTER(length * src_type) dst_type = POINTER(length * 2 * dst_type) a = cast(params.a, src_type).contents b = cast(params.b, src_type).contents result = cast(params.result, dst_type).contents # Assumes we are converting int to float or back... func = float if source_ints else int result[0::2] = map(func, a) result[1::2] = map(func, b) class _BNG_FFIParams(Structure): _fields_ = [("source_ints", c_uint32), ("len", c_size_t), ("a", c_void_p), ("b", c_void_p), ("result", c_void_p)] class _BNG_FFI(object): int_type = c_uint32 float_type = c_float _array_type = type(10 * int_type) # This assumes we want the result to be opposite type. # Maybe I misunderstood this -- easily fixable if so. _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) self._converter = lib.convert_to_bng def _getarray(self, seq, data_type): # Optimization for pre-allocated correct array 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)) # This has the salutary side-effect of insuring we were # passed a valid type result = (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) params.result = cast(pointer(result), c_void_p) self._converter(self._pointer) evens = islice(result, 0, None, 2) odds = islice(result, 1, None, 2) result = list(izip(evens, odds)) # If you have to have the converter allocate memory, # deallocate it here... return result convert = _BNG_FFI() if _test_standalone: 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))