Cython - реализация обратных вызовов - PullRequest
8 голосов
/ 09 марта 2011

Я работал с Cython, пытаясь взаимодействовать с библиотекой, написанной на c ++.Пока что дела идут довольно хорошо, и я могу эффективно использовать MOST-функции в библиотеке.Моя единственная проблема заключается в реализации обратных вызовов.В библиотеке есть 4 определения функций, которые выглядят примерно так:

typedef void (*Function1)(const uint16_t *data, 
                         unsigned width, unsigned height);
void SetCallBack(Function1);

Так что для их реализации я решил, что я сделаю что-то подобное с cython:

ctypedef void (*Function1)(unsigned short *data, 
                         unsigned width, unsigned height);
cdef extern from "lib.hpp":
    void SetCallBack(Function1)

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

def PySetCallBack(Func):
    SetCallBack(Func)

, но это дает мне (предсказуемую) ошибку:

"Невозможно преобразовать объект Python в 'Function1'"

, так что да, вот где я.Если у кого-то есть опыт настройки обратных вызовов в Cython, я был бы очень признателен за любую помощь.Спасибо.

Редактировать : Следуя вашему совету, я создал промежуточную функцию с помощью cdef, которая выглядит следующим образом:

cdef void cSetCallBack(Function1 function):
    SetCallBack(function)

Это, похоже, до меня дошло... ближе?По крайней мере, теперь появляется другая ошибка:

error: invalid conversion from ‘void (*)(short unsigned int*, unsigned int, unsigned int)’ to ‘void (*)(const uint16_t*, unsigned int, unsigned int)’

Теперь, насколько я могу судить, эти типы идентичны, поэтому я не могу понять, что происходит.

Edit2 : исправил эту проблему, объявив новый typedef:

ctypedef unsigned short uint16_t

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

Итак, я почти вернулся к тому, с чего начал.Единственное, что я могу сделать сейчас - это явно привести объект python, поступающий как функция переменного тока, но, честно говоря, я не знаю, как бы я поступил об этом.

Редактироватьтретий : Хорошо, после разбора вашего ответа я, наконец, получил его, и он работает, так что ура и еще много чего.Я закончил тем, что создал такую ​​функцию:

cdef void cSetCallback(Function1 function):
    SetCallback(function)
cdef void callcallback(const_ushort *data, unsigned width, unsigned height):
    global callbackfunc
    callbackfunc(data,width,height)
cSetCallback(callcallback)
def PySetCallback(callbackFunc):
    global callbackfunc
    callbackfunc = callbackFunc

Так что теперь единственная проблема в том, что он не может преобразовать данные const_ushort * в объект python, но это совершенно другая проблема, так что я думаю,это решено, большое спасибо.

Ответы [ 2 ]

9 голосов
/ 10 октября 2012

Я недавно был в ситуации, когда мне также приходилось связывать существующую библиотеку C ++ с Python, используя Cython, интенсивно используя события / обратные вызовы.Было нелегко найти источники об этом, и я хотел бы собрать все это здесь:

Прежде всего, класс обратного вызова C ++ для переноса (основанный на 'double ( METHOD) (void) 'прототип, но он мог быть шаблонизирован, поскольку Cython может обрабатывать шаблоны):

ALabCallBack.h:

#ifndef ALABCALLBACK_H_
#define ALABCALLBACK_H_


#include <iostream>

using namespace std;

namespace elps {

//template < typename ReturnType, typename Parameter >
class ALabCallBack {
public:

    typedef double (*Method)(void *param, void *user_data);

    ALabCallBack();
    ALabCallBack(Method method, void *user_data);
    virtual ~ALabCallBack();

    double cy_execute(void *parameter);

    bool IsCythonCall()
    {
        return is_cy_call;
    }

protected:

    bool is_cy_call;

private:

    //void *_param;
    Method _method;
    void *_user_data;

};


} /* namespace elps */
#endif /* ALABCALLBACK_H_ */

ALabCallBack.cpp:

#include "ALabCallBack.h"

namespace elps {


ALabCallBack::ALabCallBack() {
    is_cy_call = true;
};

ALabCallBack::~ALabCallBack() {
};

ALabCallBack::ALabCallBack(Method method, void *user_data) {
    is_cy_call = true;
    _method = method;
    _user_data = user_data;
};

double ALabCallBack::cy_execute(void *parameter)
{
    return _method(parameter, _user_data);
};


} /* namespace elps */

Где:

  • 'callback' :: Метод шаблона / конвертера для вызова метода объекта Python (= Method) из информации, напечатанной на C

  • 'method' :: Эффективный метод, переданный пользователем Python (= user_data)

  • 'parameter' :: Параметр, который будет передан в 'method'

Теперь нам нужно реализовать файл .pyx ...

Наш базовый прототип:

ctypedef double (*Method)(void *param, void *user_data)

Затем мы предоставляем оболочку Cython дляКласс C ++:

cdef extern from "../inc/ALabCallBack.h" namespace "elps" :
    cdef cppclass ALabCallBack:
        ALabCallBack(Method method, void *user_data)
        double cy_execute(void *parameter)

Метод шаблона / конвертера, который будет использоваться для перевода набранного прототипом C в вызов объекта Python:

cdef double callback(void *parameter, void *method):
    return (<object>method)(<object>parameter)

Теперь давайте встроим эти функции в класс Cython:

cdef class PyLabCallBack:
    cdef ALabCallBack* thisptr

    def __cinit__(self, method):
        # 'callback' :: The pattern/converter method to fire a Python 
        #               object method from C typed infos
        # 'method'   :: The effective method passed by the Python user 
       self.thisptr = new ALabCallBack(callback, <void*>method)

    def __dealloc__(self):
       if self.thisptr:
           del self.thisptr

    cpdef double execute(self, parameter):
        # 'parameter' :: The parameter to be passed to the 'method'
        return self.thisptr.cy_execute(<void*>parameter)

Редактирование: Лучшая типизация для функции execute: def execute => cpdef double

Вот и все.Назовите это следующим образом:

def func(obj):
    print obj 
    obj.Test()     # Call to a specific method from class 'PyLabNode'
    return obj.d_prop

n = PyLabNode()    # Custom class of my own
cb = PyLabCallBack(func)
print cb.execute(n)

Поскольку python неявно типизирован, мы можем получить доступ к свойствам объекта 'obj', относящимся к классу объекта, переданного в качестве аргумента, когда наступает времяобратный вызов.

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

5 голосов
/ 09 марта 2011

Если вы можете изменить библиотеку, чтобы определить:

typedef void (*Function1)(const uint16_t *data, 
                          unsigned width, unsigned height,
                          void *user_data);
void SetCallBack(Function1, void*);

, боюсь, вам не повезло.Если у вас есть void*, то вы определяете функцию, которая вызывает вызываемый Python объект с правильными аргументами и SetCallBack с этой функцией и вызываемым Python.

Если вы не можете, но обратный вызовglobal (кажется), вы можете создать глобальную переменную для хранения объекта python. Чем вы снова создадите функцию для вызова объекта python и передачи его в SetCallBack, а ваш PySetCallback просто установитглобальный и гарантирует регистрацию правильной функции.

Если обратный вызов является контекстно-зависимым, но у вас нет возможности передать ему указатель «пользовательских данных», я боюсь, что вам не повезло.

Я знаю Python и C ++, но не Cython, поэтому я не знаю, можете ли вы создать функцию в Cython или вам придется писать в C ++.

...