2015-07-09 2 views
30

я определил ctypes класс и связанный с ним удобство функции, как так:Чистый способ структурирования 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),) 

я затем определить простую функцию удобства:

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

Большинство этих работ но у меня есть два вопроса:

  • Это не достаточно гибкий; Я хотел был бы иметь возможность создать экземпляр BNG_FFITuple, используя c_float вместо c_uint32 (так что поля c_float), и наоборот, BNG_FFIArraydata_type - c_uint32. Однако я не знаю, как это сделать.
  • Я хочу освободить память, которая теперь принадлежит Python, отправив POINTER(BNG_FFIArray) обратно в мой dylib (см. drop_array - Я уже определил функцию в своем dylib), но я не уверен, что Я должен это назвать.

Есть ли способ инкапсулировать все это в более аккуратный, более Pythonic способ, который также безопаснее? Я обеспокоен тем, что без очистки памяти определяется в прочном образом (на __exit__? __del__?), Что все, что идет не так, приведет к unfreed памяти

+5

Вам нужен 'BNG_FFITuple' как аргумент FFI или он предназначен только для использования в Python? Если это просто используется в Python, вам лучше будет работать [collection.namedtuple] (https://docs.python.org/3/library/collections.html#collections.namedtuple). Просто определите отдельную функцию 'errcheck' для конверсий' int' и 'float'. Вы можете освободить массив в 'BNG_FFIArray .__ del__', но используйте ссылку на класс' lib.drop_array' как 'BNG_FFIArray._drop_array', чтобы избежать проблем с установкой модуля' lib' в 'None' перед финализатором' __del__' объекта ха. – eryksun

+3

Я не уверен, что понимаю; мои функции dylib ожидают структуру с полями 'data' и' len' с соответствующими типами, но ее не нужно называть чем-то конкретным. – urschrei

+4

Вы преобразовываете результат в массив 'BNG_FFITuple' в' bng_void_array_to_tuple_list'. Вы когда-нибудь передавали 'BNG_FFITuple' обратно в свою библиотеку? Если нет, нет причин использовать структуру ctypes для этого вместо преобразования результата в обычный кортеж Python или 'namedtuple'. После преобразования 'BNG_FFIArray' является единственной ссылкой на массив, поэтому отлично использовать финализатор' __del__' для вызова 'drop_array'. – eryksun

ответ

3

Поскольку у вас есть некоторый контроль над стороной ржавчины, чистейшая вещь do было бы предварительно выделить массив результатов из Python перед вызовом и передать все в одной структуре.

Приведенный ниже код предполагает эту модификацию, но также обозначает место, где вы делаете освобождение, если вы не можете этого сделать.

Обратите внимание: если вы выполняете такую ​​инкапсуляцию, вам не нужно указывать такие параметры, как параметры и обработку результатов для библиотечной функции, поскольку вы вызываете фактическую функцию только из одного места и всегда точно те же самые параметры.

Я не знаю, ржавчины (и даже мой C является немного ржавый), но ниже код предполагает, что вы переопределить вашу ржавчину, чтобы соответствовать эквивалент что-то вроде этого:

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

void convert_to_bng(FFIParams *p) { 
} 

Вот Python , Одно последнее замечание - это не потокобезопасность из-за повторного использования структуры параметров. Это достаточно легко исправить, если необходимо.

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

Здесь представлена ​​измененная версия кода, который выделяет возвращаемый массив в вызываемой DLL. Так как это было бы труднее проверить с чистого Python, и так как я не знаю, ржавчины, я построил сырный библиотеку 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): 
     # 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)) 

И вот результат, когда я выполнил его:

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

+0

Этот пример достаточно прост, вам не нужна структура - - вы можете просто передать 4 параметра напрямую. Но ваш первоначальный вопрос имел структуру, и я решил оставить его в качестве примера для более сложных случаев. Если вы хотите передать более одного параметра, просто сделайте это - ctypes не ищет _require_ для определения допустимых типов в каждой позиции, это просто _allows_ it, что действительно полезно, когда вы подвергаете функцию ac более высокоуровневому коду, но не так полезно, когда вы его обертываете и только вызываете его из одного места. –

Смежные вопросы