Встраивание CPython: как создать вызовы Python, чтобы обернуть указатели обратного вызова C? - PullRequest
0 голосов
/ 29 августа 2018

Предположим, я встраиваю интерпретатор CPython в большую программу, написанную на C. Компоненту C программы иногда требуется вызывать функции, написанные на Python, предоставляя им функции обратного вызова в качестве аргументов.

Используя CPython , расширяющий и встраивающий API-интерфейсы , как мне создать "вызываемый" объект Python, который оборачивает указатель на функцию C, чтобы я мог передать этот объект в код Python и получить Код Python успешно перезвонил в код C?

Примечание: это пересмотренная версия вопроса , первоначально отправленного пользователем dhanasubbu , на который я ответил, но который затем был удален. Я думаю, что на самом деле это был хороший вопрос, поэтому я преобразовал то, что написал, в ответ на свой вопрос. Альтернативные ответы приветствуются.

Ответы [ 2 ]

0 голосов
/ 02 сентября 2018

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

Я написал рабочий пример на Cython, поскольку это простой способ смешивания Python и C, но он должен показать, как использовать эти объекты:

cdef extern from *:
    """
    int f(int x) {
       return x*2;
    }
    """
    int f(int f)

Я определяю пример функции f (в строке документации, которую Cython включает непосредственно в скомпилированный файл).

import ctypes
from libc.stdint cimport intptr_t

def make_f_wrapper():
    func_type = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int)
    cdef intptr_t f_ptr = <intptr_t>&f
    return func_type(f_ptr)

Это версия Cython (довольно близкая к синтаксису Python) создания указателя ctypes для f. Я определяю аргументы функции, преобразую указатель f в целое число соответствующего размера, а затем инициализирую объект-оболочку этим целым числом.

cdef extern from *:
    """
    PyObject* make_f_wrapper_c_impl() {
        PyObject *ctypes_module = NULL, *CFUNCTYPE = NULL, *c_int = NULL, 
                 *func_type = NULL, *ptr_value = NULL, *result = NULL;

        ctypes_module = PyImport_ImportModule("ctypes");
        if (ctypes_module == NULL) goto cleanup;
        CFUNCTYPE = PyObject_GetAttrString(ctypes_module,"CFUNCTYPE");
        if (CFUNCTYPE == NULL) goto cleanup;
        c_int = PyObject_GetAttrString(ctypes_module,"c_int");
        if (c_int == NULL) goto cleanup;
        func_type = PyObject_CallFunctionObjArgs(CFUNCTYPE,c_int,c_int,NULL);
        if (func_type == NULL) goto cleanup;
        ptr_value = PyLong_FromVoidPtr(&f);
        if (ptr_value == NULL) goto cleanup;
        result = PyObject_CallFunctionObjArgs(func_type,ptr_value,NULL);

        cleanup:
        Py_XDECREF(ptr_value);
        Py_XDECREF(func_type);
        Py_XDECREF(c_int);
        Py_XDECREF(CFUNCTYPE);
        Py_XDECREF(ctypes_module);
        return result;
    }
    """
    object make_f_wrapper_c_impl()

def make_f_wrapper_c():
    return make_f_wrapper_c_impl()

Приведенный выше код является переводом C-кода вышеупомянутого кода Pythony - он делает то же самое, но немного сложнее, поскольку использует C API. Он просто использует модуль ctypes через интерфейс Python. Еще раз код C встраивается в файл Cython через строку документации; однако подобный код может быть использован в непосредственно написанном модуле C API.

(Все эти фрагменты Cython объединяются в один длинный рабочий пример)

0 голосов
/ 29 августа 2018

Чтобы определить тип расширения, который может быть вызван в том смысле, в котором Python использует это термин, вы заполняете слот tp_call объекта типа, который является эквивалентом C специального метода __call__. Функция, которая входит в этот слот, будет клейкой подпрограммой, которая вызывает реальный обратный вызов C. Вот код для простейшего случая, когда обратный вызов C не принимает аргументов и ничего не возвращает.

typedef struct {
    PyObject_HEAD
    /* Type-specific fields go here. */
    void (*cfun)(void);  /* or whatever parameters it actually takes */
} CallbackObj;

static PyObject *Callback_call(PyObject *self, PyObject *args, PyObject *kw)
{
    /* check that no arguments were passed */
    const char no_kwargs[] = { 0 };
    if (!PyArg_ParseTupleAndKeywords(args, kw, "", no_kwargs))
        return 0;

    CallbackObj *cself = (CallbackObj *)self;
    cself->cfun();
    Py_RETURN_NONE;
}

static PyTypeObject CallbackType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "mymodule.Callback",
    .tp_doc = "Callback function passed to foo, bar, and baz.",
    .tp_basicsize = sizeof(CallbackObj),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_new = PyType_GenericNew,
    .tp_call = Callback_call,
};

Создайте объект типа с PyType_Ready как обычно. Не помещайте его в любой модуль, видимый для Python, потому что код Python не может правильно создавать экземпляры этого типа. (Из-за этого мне не надоела функция tp_init; просто убедитесь, что вы всегда инициализируете ->cfun после создания экземпляров из C, иначе Callback_call вылетит.)

Теперь предположим, что фактическая функция, которую нужно вызвать, называется real_callback, а функция Python, которой вы хотите ее передать, называется function_to_call. Сначала вы создаете один из объектов обратного вызова, вызовите объект типа, как обычно, и инициализируйте его поле ->cfun:

 PyObject *args = PyTuple_New(0);
 CallbackObj *cb = (CallbackObj *)PyObject_CallObject(
     (PyObject *)CallbackType, args);
 Py_DECREF(args);
 cb->cfun = real_callback;

Затем вы помещаете cb в кортеж аргумента и вызываете функцию Python. возражать с этим, как обычно.

 args = Py_BuildValue("(O)", cb);
 PyObject *ret = PyObject_CallObject(function_to_call, args);
 Py_DECREF(args);
 Py_DECREF(cb);
 // do stuff with ret, here, perhaps
 Py_DECREF(ret);

Распространение этого на более сложные случаи, когда обратный вызов C должен принимать аргументы и / или возвращать значения и / или вызывать исключения Python при ошибках и / или получать информацию «замыкания» из внешнего контекста, оставляется в качестве упражнения.

...