2015-07-09 12 views
30

ja zdefiniował ctypes klasę i wygodę przypisaną funkcję tak:Clean sposób strukturyzacji ctypes klasa

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

I następnie zdefiniować prostą funkcję Wygoda:

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

Większość tych prac idealnie, ale mam dwie kwestie:

  • Nie jest wystarczająco elastyczny; Chciałbym móc utworzyć instancję używając c_float zamiast c_uint32 (więc pola są c_float) i odwrotnie, więc BNG_FFIArraydata_type jest c_uint32. Nie wiem jednak, jak to zrobić.
  • Chciałbym zwolnić pamięć, która jest teraz własnością Pythona, wysyłając POINTER(BNG_FFIArray) z powrotem do mojej dylib (patrz drop_array - Mam już zdefiniowaną funkcję w mojej dylib), ale nie jestem pewien, na co punkt, powinienem to nazwać.

Czy istnieje sposób hermetyzowania tego wszystkiego w sposób bardziej przejrzysty, bardziej Pythoniczny, który jest również bezpieczniejszy? Jestem zaniepokojony, że bez czyszczenia pamięci jest zdefiniowany w solidny sposób (na __exit__? __del__?) Że wszystko, co pójdzie nie tak, doprowadzi do braku pamięci

+5

Czy potrzebujesz 'BNG_FFITuple' jako argumentu FFI, czy jest to tylko do użycia w Pythonie? Jeśli jest to po prostu używane w Pythonie, lepiej będzie Ci służyć [collections.namedtuple] (https://docs.python.org/3/library/collections.html#collections.namedtuple). Po prostu zdefiniuj oddzielną funkcję 'errcheck' dla konwersji' int' i 'float'. Możesz zwolnić tablicę w 'BNG_FFIArray .__ del__', ale użyj odwołania do klasy' lib.drop_array' jako 'BNG_FFIArray._drop_array' aby uniknąć problemów z odejściem modułu ustawienie' lib' na 'None' przed finalizatorem' __del__' obiektu Zostałem wezwany. – eryksun

+3

Nie jestem pewien, czy rozumiem; moje funkcje dylib oczekują struktury z polami "data" i "len" z odpowiednimi typami, ale nie trzeba jej nazwać niczym konkretnym. – urschrei

+4

Konwertujesz wynik na tablicę 'BNG_FFITuple' w' bng_void_array_to_tuple_list'. Czy kiedykolwiek przekazujesz 'BNG_FFITuple' do swojej biblioteki? Jeśli nie, nie ma powodu, aby używać struktury ctypes dla tego zamiast konwertowania wyniku do zwykłego "krotki" Pythona lub "nazwanego krotki". Po przekonwertowaniu 'BNG_FFIArray' jest jedynym odniesieniem do tablicy, więc dobrze jest użyć jego' __del__' finalizatora, aby wywołać 'drop_array'. – eryksun

Odpowiedz

3

Ponieważ masz pewną kontrolę nad stroną rdzy, najczystszą rzeczą do byłoby wcześniej wstępnie przydzielić tablicę wyników z Python przed wywołaniem i przekazać wszystko w pojedynczej strukturze.

Poniższy kod zakłada tę modyfikację, ale także oznacza miejsce, w którym dokonanoby sprzedaży, jeśli nie można tego zrobić.

Należy pamiętać, że w przypadku tego rodzaju enkapsulacji NIE trzeba określać rzeczy takich jak parametry i przetwarzanie wyników dla funkcji biblioteki, ponieważ wywołuje się tylko rzeczywistą funkcję z jednego miejsca i zawsze dokładnie te same rodzaje parametrów.

nie wiem rdzę (i nawet mój C jest nieco zardzewiały), ale poniżej kod zakłada przedefiniować swoją rdzę dopasować równowartość mniej więcej tak:

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

void convert_to_bng(FFIParams *p) { 
} 

Oto Python . Ostatnia uwaga - nie jest bezpieczna dla wątków, ze względu na ponowne wykorzystanie struktury parametrów. W razie potrzeby można to naprawić.

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

Oto zmodyfikowana wersja kodu, który przydziela tablicę zwrotów w wywołanej bibliotece DLL. Ponieważ byłoby to trudniejsze do przetestowania z czystym Pythonie, a ponieważ nie wiem, rdzę, zbudowałem kiepskie biblioteka C do właściwego testu:

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

Oto kod Pythona, który nazywa go:

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

A oto wynik, kiedy wykonywane go:

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

dzieje się układ 32-bitowy Ubuntu 14.04. Użyłem Pythona 2.7, i zbudowałem bibliotekę z gcc --shared ffitest.c -o testlib.so -Wall

+0

Ten przykład jest wystarczająco prosty, nie potrzebowałbyś struktury - - możesz po prostu przekazać bezpośrednio 4 parametry. Ale twoje pierwotne pytanie miało strukturę i postanowiłem zostawić je jako przykład dla bardziej skomplikowanych spraw. Jeśli chcesz przekazać więcej niż jeden parametr, po prostu zrób to - ctypes nie żąda zdefiniowania poprawnych typów w każdej pozycji, po prostu _właśnie_ to, co jest bardzo przydatne, gdy wystawiasz funkcję ac na kod wyższego poziomu, ale nie jest tak przydatny, gdy owijasz go i wywołujesz tylko z jednego miejsca. –