2011-03-09 12 views
8

Pracowałem z Cythonem, próbując połączyć się z biblioteką napisaną w języku C++. Do tej pory wszystko idzie całkiem nieźle i mogę skutecznie korzystać z MOST funkcji w bibliotece. Mój jedyny problem leży w implementacji wywołań zwrotnych. Biblioteka posiada 4 definicje funkcji, które wyglądają trochę coś takiego:Cython - implementowanie wywołań zwrotnych

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

Więc do wprowadzania ich Pomyślałem chciałbym zrobić coś takiego z Cython:

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

Które faktycznie kompiluje poprawnie, jednak Nie mogę na całe życie pomyśleć, jak to zaimplementować w taki sposób, aby zadziałało wywołanie zwrotne. Po raz pierwszy spróbował tworząc funkcję, która po prostu zadzwonić, że podobnie jak w jaki sposób to zrobić dla każdej innej funkcji, wymyślanie to:

def PySetCallBack(Func): 
    SetCallBack(Func) 

ale to daje mi (przewidywalne) Błąd:

"Nie można przekonwertować obiektu Pythona na" Funkcję 1 ""

więc tak, właśnie w tym jestem. Jeśli ktokolwiek ma doświadczenie w tworzeniu wywołań zwrotnych w Cython, byłbym bardzo wdzięczny za jakąkolwiek pomoc. Dzięki.

Edit: Po poradę, stworzyłem pośrednią funkcję z cdef, który wygląda tak:

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

Wydaje mi się zdobyć ... bliżej? Otrzymuję teraz inny błąd co najmniej:

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

Teraz, o ile mogę powiedzieć, że te typy są identyczne, więc nie mogę określić, co się dzieje.

Edit2: Poprawiono ten problem poprzez uznanie nowego typedef:

ctypedef unsigned short uint16_t 

i używając które jako argument zadzwonić, ale widocznie to nie był rzeczywiście coraz bliżej, ale tylko przy mnie dookoła poboczna ścieżka, ponieważ próbując wywołać tę funkcję, otrzymuję taki sam komunikat "Nie można ponownie przekonwertować obiektu Pythona na błąd" Function1 ".

Więc, jestem już z powrotem tam, gdzie zacząłem. Jedyne, co mogę teraz zrobić, to jawnie rzucić obiekt python jako funkcję c, ale szczerze mówiąc, nie mam pojęcia, jak bym to robił.

Edycja trzecia: porządku, po krajanie odpowiedź I wreszcie dostać to, i to działa, więc hurra i etażerka. Co skończyło się robi było stworzenie funkcję tak:

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 

Więc teraz jedynym problemem jest to, że nie można przekonwertować const_ushort * dane w obiekt Pythona, ale to już inny problem całkowicie, więc myślę, że ten jeden jest rozwiązany, wielkie dzięki.

+0

Zmień 'short unsigned int *' na 'const short unsigned int *'. – aschepler

+1

Zgodnie z moją wiedzą, Cython nie ma pojęcia, co oznacza const, a gdy próbuję go użyć, otrzymuję "Const nie jest identyfikatorem typu". – Josiah

Odpowiedz

5

Jeśli można zmodyfikować biblioteki, aby określić:

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

zamiast obawiam jesteś pecha. Jeśli masz numer void*, to możesz zdefiniować funkcję, która wywołuje obiekt wywoływalny pythona z poprawnymi argumentami i SetCallBack z tą funkcją i podpowiadaniem Pythona.

Jeśli nie możesz, ale wywołanie zwrotne jest globalne (wydaje się, że jest), możesz utworzyć globalną zmienną do przechowywania obiektu Pythona. Następnie musisz ponownie utworzyć funkcję wywołania obiektu Pythona i przekazać go to do SetCallBack, a Twoja PySetCallback ustawi globalnie i zapewni, że właściwa funkcja zostanie zarejestrowana.

Jeśli wywołanie zwrotne jest zależne od kontekstu, ale nie masz możliwości przekazania go jako wskaźnika "danych użytkownika", obawiam się, że nie masz szczęścia.

Znam Pythona i C++, ale nie cython, więc nie wiem, czy można utworzyć funkcję w cytoncie, czy trzeba pisać w C++.

8

Niedawno byłem w sytuacji, w której musiałem również połączyć istniejącą bibliotekę C++ z Pythonem za pomocą Cythona, intensywnie wykorzystując zdarzenia/wywołania zwrotne. To nie było tak łatwo znaleźć źródła na ten temat i chciałbym umieścić to wszystko razem tutaj:

Przede wszystkim owijania klasa C++ wywołania zwrotnego (w oparciu o „podwójnym (metoda) (void)” prototyp , ale to mogło być templatized, ponieważ Cython może obsługiwać szablony):

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 */ 

Gdzie:

  • 'zwrotna' :: Sposób wzór/konwerter do ognia Python (= metoda) metodę obiektu z C wpisane informacje

  • 'metoda' :: Skuteczna metoda przekazywana przez użytkownika Python (= User_Data)

  • 'parametru' :: parametr być przekazywana do 'metody'

Teraz musimy zaimplementować plik .pyx ...

Nasz prototyp podstawa:

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

Następnie zapewniamy owijkę Cython dla klasy C++

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

Metoda wzór/konwerter, który będzie używany do tłumaczenia C wpisane prototyp w Pythonie wywołanie obiektu:

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

Teraz załóżmy to funkcje w klasie 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) 

Edycja: Better wpisując do wykonywania funkcji: def wykonać => cpdef podwójnym

to wszystko. Nazwij to jak robi coś takiego:

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) 

jak Python jest domyślnie wpisane, możemy uzyskać dostęp do właściwości „obj” obiektu związanego z klasą obiektu przekazany jako argument, kiedy nadejdzie czas, aby ogień zwrotnego .

Można go dość łatwo zaadaptować do czystej implementacji C. Proszę, powiedz mi, czy widzisz jakieś możliwe ulepszenie (w moim przypadku perfy są bardzo ważne, ponieważ zdarzenia są intensywnie wystrzeliwane).

Powiązane problemy