Обратное приведение ctypes.py_object в обратный вызов - PullRequest
5 голосов
/ 14 июля 2010

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

Обратный вызов имеет подпись:

void cb(f *beingdestroyed);

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

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

Ответы [ 3 ]

6 голосов
/ 29 июля 2010

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

  • Прототип обратного вызова должен указывать c_void_p для параметра контекста
  • Аргументы для библиотечной функции должны указывать 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
2 голосов
/ 15 июля 2010

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

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

1 голос
/ 05 июня 2014

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

Я получил большую пользу от первоначального ответа Боба (как только я прошел все кролики), так что спасибо!

...