2010-07-14 3 views
2

Я пытаюсь обернуть библиотеку C с помощью ctypes. Одной из особенностей библиотеки является обратный вызов ondestroy, который вызывается, когда дескриптор, возвращаемый библиотекой, вот-вот будет уничтожен.Back-casting ctypes.py_object в обратном вызове

Обработчик имеет подпись:

void cb(f *beingdestroyed); 

API-интерфейс позволяет связать указанного пользователем void * с е, когда он возвращается в библиотеку. Следовательно, я могу связать py_object, который используется для его переноса в качестве пользовательских данных. Мой план состоит в том, чтобы иметь поле is_valid и когда обратный вызов запускается, чтобы извлечь user_data и установить для этого поля значение false.

Моя проблема заключается в том, как вывести мой высокоуровневый py_object; Я могу получить пользовательские данные как ctypes.void_p и отнести к ctypes.py_object, но тогда у меня есть только API Python C для работы. Можно выполнить обратный вызов на объект высокого уровня, с которым я могу работать, написав user_object.is_valid = 0?

ответ

3

Чтобы уточнить ответ Томаса Хеллера:

  • Обратный вызов прототип должен указать c_void_p для параметра контекста
  • В argtypes для библиотечной функции следует указать py_object для параметра контекста
  • Эта функция должна быть вызванный с py_object(my_python_context_object)
  • Ваша реализация функции обратного вызова на языке Python должна передать контекст в py_object и извлечь его значение: cast(context, py_object).value

Вот рабочий пример. Начните с источником C для простой DLL:

// FluffyBunny.c 
// Compile on windows with command line 
//  cl /Gd /LD FluffyBunny.c 
// Result is FluffyBunny.DLL, which exports one function: 
//  FluffyBunny() uses __cdecl calling convention. 

#include <windows.h> 

BOOL APIENTRY DllMain(HMODULE, DWORD, LPVOID) { 
    return TRUE; 
} 

typedef int (*FLUFFYBUNNY_CALLBACK)(void *context); 

__declspec(dllexport) int FluffyBunny(FLUFFYBUNNY_CALLBACK cb, void *context) { 
    int result = 0; 
    int count = 0; 
    if (cb) { 
    while (result == 0) { 
     result = (*cb)(context); 
     ++count; 
    } 
    } 
    return count; 
} 

А вот программа Python, которая вызывает DLL:

# FluffyBunny.py 
from ctypes import * 

# Declare a class that will be used for context info in calls to FluffyBunny() 
class Rabbit: 
    def __init__(self): 
     self.count = 0 

# FluffyBunny() wants a callback function with the following C prototype: 
#  typedef int (*FLUFFYBUNNY_CALLBACK)(void *context); 
FLUFFYBUNNY_CALLBACK = CFUNCTYPE(c_int, c_void_p) 

# This DLL has been compiled with __cdecl calling convention. 
FluffyBunny_dll = CDLL('FluffyBunny.dll') 

# Get the function from the library. Its C prototype is: 
#  int FluffyBunny(FLUFFYBUNNY_CALLBACK cb, void *context); 
# Note that I use "py_object" instead of "c_void_p" for the context argument. 
FluffyBunny   = FluffyBunny_dll.FluffyBunny 
FluffyBunny.restype = c_int 
FluffyBunny.argtypes = [FLUFFYBUNNY_CALLBACK, py_object] 

# Create Python version of the callback function. 
def _private_enumerateBunnies(context): 
    # Convert the context argument to a py_object, and extract its value. 
    # This gives us the original Rabbit object that was passed in to 
    # FluffyBunny(). 
    furball = cast(context, py_object).value 
    # Do something with the context object. 
    if furball: 
     furball.count += 1 
     print 'furball.count =', furball.count 
     # Return non-zero as signal that FluffyBunny() should terminate 
     return 0 if (furball.count < 10) else -1 
    else: 
     return -1 

# Convert it to a C-callable function. 
enumerateBunnies = FLUFFYBUNNY_CALLBACK(_private_enumerateBunnies) 

# Try with no context info. 
print 'no context info...' 
result = FluffyBunny(enumerateBunnies, None) 
print 'result=', result 

# Give it a Python object as context info. 
print 'instance of Rabbit as context info...' 
furball = Rabbit() 
result = FluffyBunny(enumerateBunnies, py_object(furball)) 
print 'result=', result 
+0

Спасибо, это было именно то, что мне было нужно. –

1

Обычный способ - полностью исключить эту проблему.

Используйте метод вместо функции обратного вызова в качестве, и неявное self позволит получить доступ к user_data поля.

1

Я применил информацию в ответ Боба Pyron к моему коду, но не сделал например, обратный вызов должен был знать, что он вызывается программой C, и имеет дело с ctypes-stuff. Оказывается, что с незначительным изменением этот _private_enumerateBunnies() получает объект python вместо void *. Я сделал эквивалент этого в моем коде:

FLUFFYBUNNY_CALLBACK = CFUNCTYPE (c_int, py_object)

И тогда все C-фанк-материал был скрыт в коде, который уже должен был справиться с этим, и он не распространился среди пользователей этого API (то есть провайдеров обратных вызовов). Конечно, тогда вы должны передать ему объект python, но это довольно небольшое ограничение.

Мне очень понравился исходный ответ Боба (однажды я прошел мимо всех кроликов), так что спасибо!

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