2016-04-28 2 views
2

Я написал обертку для SDK Corsair Utility Engine, но есть одна функция, которую я не смог обернуть. Это асинхронная функция, которая принимает функцию обратного вызова, но я не могу понять, как это сделать.Как создать функцию обратного вызова с помощью ctypes в Python?

Функция выглядит следующим образом:

bool CorsairSetLedsColorsAsync(int size, CorsairLedColor* ledsColors, void (*CallbackType)(void* context, bool result, CorsairError error), void *context) 

Эти реализации, которые я пытался до сих пор:

def SetLedsColorsAsync(self, size, led_color, callback, context): 
    c_func = CFUNCTYPE(c_void_p, c_void_p, c_bool, c_int) 
    c_callback = c_func(callback) 
    self._libcue.CorsairSetLedsColorsAsync.restype = c_bool 
    self._libcue.CorsairSetLedsColorsAsync.argtypes = [c_int, POINTER(CorsairLedColor), c_void_p, c_void_p] 
    return self._libcue.CorsairSetLedsColorsAsync(size, led_color, c_callback, context) 

, а также

def SetLedsColorsAsync(self, size, led_color, callback, context): 
    c_func = CFUNCTYPE(None, c_void_p, c_bool, c_int) 
    c_callback = c_func(callback) 
    self._libcue.CorsairSetLedsColorsAsync.restype = c_bool 
    self._libcue.CorsairSetLedsColorsAsync.argtypes = [c_int, POINTER(CorsairLedColor), c_func, c_void_p] 
    return self._libcue.CorsairSetLedsColorsAsync(size, led_color, c_callback, context) 

Код I с тестом

from cue_sdk import * 
import time 


def test(context, result, error): 
    print context, result, error 
    return 0 

Corsair = CUE("CUESDK.x64_2013.dll") 
Corsair.RequestControl(CAM_ExclusiveLightingControl) 
Corsair.SetLedsColorsAsync(1, CorsairLedColor(CLK_H, 255, 255, 255), test, 1) 

while True: 
time.sleep(1) 

time.sleep() есть только для того, чтобы программа оставалась живой.

При запуске он сбой с кодом ошибки 3221225477 (STATUS_ACCESS_VIOLATION) на Windows.

If you need to see the actual wrapper, you can find it here.

+0

@ IgnacioVazquez-Abrams Я не уверен, что вы имеете в виду, вы можете разработать? – 10se1ucgo

+0

Как вы определяете 'CallbackType'? – tynn

+0

@tynn [Это документация для функции] (http://i.imgur.com/ZRsrSPW.png), CallbackType не упоминается нигде в руководстве. – 10se1ucgo

ответ

0

Я полностью забыл о проблеме с сборкой мусора, пока эриксун напомнил мне. Он предположил, что я создаю постоянный обработчик обратного вызова, который будет хранить все обратные вызовы и вызывать + всплывать при необходимости. Это то, что я сделал.

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

self._callback_type = CFUNCTYPE(None, c_void_p, c_bool, c_int) 
self._callback = self._callback_type(self._callback_handler) 
self._libcue.CorsairSetLedsColorsAsync.restype = c_bool 
self._libcue.CorsairSetLedsColorsAsync.argtypes = [c_int, POINTER(CorsairLedColor), self._callback_type, c_void_p] 

_callback_handler функция выглядит следующим образом:

def _callback_handler(self, context, result, error): 
    if context is not None and context in self._callbacks: 
     self._callbacks.pop(context)(context, result, error) 

Реальная функция выглядит следующим образом.

def SetLedsColorsAsync(self, size, led_color, callback=None, context=None): 
    if callback: 
     if context is None: 
      context = id(callback) 
     self._callbacks[context] = callback 
    return self._libcue.CorsairSetLedsColorsAsync(size, led_color, self._callback, context) 

_callback_type является фактической CFUNCTYPE, что оборачивает постоянный обратный вызов (_callback_handler), который является одним из типа аргумента прототипа в. Когда вызывается SetLedsColorsAsync, параметр callback помещается в словарь (с контекстом или идентификатором функции, являющейся ключом). Вместо того, чтобы передавать обратный вызов в функцию, вместо этого передается постоянный обратный вызов. Когда вызывается постоянный обратный вызов, он вызывается надлежащей функцией и удаляет ее из словаря.

Тест я использовал:

#!python3 
import time 

from cue_sdk import * 


def test(context, result, error): 
    print(context, result, error) 
    assert context == id(test) 


Corsair = CUE("CUESDK.x64_2013.dll") 
Corsair.RequestControl(CAM_ExclusiveLightingControl) 
Corsair.SetLedsColorsAsync(1, CorsairLedColor(CLK_H, 255, 255, 255), test) 

while True: 
    time.sleep(1) 

Пример вывода:

2969710418936 True 0 

If I'm not making sense, the commit is here.

+0

Пользовательский контекст может быть не уникальным. Я предлагаю создать кортеж '(callback, context)' и установить его 'id' как' context'. Постоянный обратный вызов будет «pop» и распаковать кортеж. – eryksun

+0

Оптимизация для передачи контекста как 'NULL' (' None'), когда пользователь не предоставляет «обратный вызов», хороша, но для этого требуется проверка ошибок. Ранние исключения будут более ясными, чем асинхронные исключения в постоянном обратном вызове. Если 'callback' является' None', тогда повышайте 'ValueError', если' context' также не 'None'. Else raise 'TypeError', если' not callable (callback) '. В противном случае сохраните кортеж в '_callbacks' своим' id', который становится «контекстом» для постоянного обратного вызова. – eryksun

+0

Я также предлагаю хранить '_libcue',' _callback' и '_callbacks' в самом классе. Нет причин устанавливать их на каждом экземпляре. – eryksun