11 Stimmen

Cython - Implementierung von Rückrufen

Ich habe mit Cython gearbeitet und versucht, eine Schnittstelle zu einer in C++ geschriebenen Bibliothek zu schaffen. Bis jetzt läuft es ganz gut, und ich kann die meisten Funktionen der Bibliothek effektiv nutzen. Mein einziges Problem liegt in der Implementierung von Rückrufen. Die Bibliothek hat 4 Funktionsdefinitionen, die ein wenig wie folgt aussehen:

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

Also, um sie zu implementieren, dachte ich, ich würde so etwas wie dieses mit Cython tun:

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

Die tatsächlich korrekt kompiliert, aber ich kann nicht für das Leben von mir denken, wie man tatsächlich implementieren, dass in einer solchen Weise, dass der Rückruf funktionieren würde. Ich habe zuerst versucht, eine Funktion zu erstellen, die nur aufrufen würde, dass, ähnlich wie Sie es für jede andere Funktion tun würde, kommen mit diesem:

def PySetCallBack(Func):
    SetCallBack(Func)

aber das gibt mir den (vorhersehbaren) Fehler:

"Python-Objekt kann nicht in 'Funktion1' umgewandelt werden"

Also ja, so weit bin ich schon. Wenn jemand Erfahrung mit der Einrichtung von Rückrufen in Cython hat, wäre ich für jede Hilfe sehr dankbar. Danke!

bearbeiten : Ihrem Rat folgend, habe ich eine Zwischenfunktion mit einem cdef erstellt, die wie folgt aussieht:

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

Das hat mich anscheinend... Näher dran? Zumindest wird jetzt ein anderer Fehler angezeigt:

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

Soweit ich das beurteilen kann, sind diese Typen identisch, so dass ich nicht herausfinden kann, was hier los ist.

Bearbeiten2 : Dieses Problem wurde durch die Deklaration eines neuen Typedefs behoben:

ctypedef unsigned short uint16_t

und mit, dass als das Argument aufzurufen, aber anscheinend, dass war nicht wirklich näher, sondern nur, die mich um ein Nebengleis, da beim Versuch, diese Funktion aufzurufen, bekomme ich die gleiche "Cannot konvertieren Python-Objekt zu 'Function1'" Fehler alle wieder.

Ich bin also so ziemlich wieder da, wo ich angefangen habe. Das einzige, was ich jetzt tun kann, ist explizit das Python-Objekt kommen in als eine C-Funktion wie das, aber, um ehrlich zu sein, ich habe keine Ahnung, wie ich über das gehen würde.

Bearbeiten Sie die dritte : In Ordnung, nach sezieren Ihre Antwort ich endlich bekommen es, und es funktioniert, so Hurra und was nicht. Was ich am Ende tat, war die Erstellung einer Funktion wie diese:

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

So jetzt das einzige Problem ist, dass es nicht const_ushort *data in ein Python-Objekt konvertieren kann, aber das ist ein anderes Problem ganz, so ich denke, dieses ist gelöst, vielen Dank.

9voto

Gauthier Boaglio Punkte 9626

Ich war kürzlich in der Situation, dass ich eine bestehende C++-Bibliothek mit Python unter Verwendung von Cython koppeln musste, wobei ich intensiv von Events/Callbacks Gebrauch machte. Es war gar nicht so einfach, Quellen dazu zu finden, und ich möchte das alles hier zusammenfassen:

Zunächst einmal ist die umhüllende C++-Callback-Klasse (basierend auf 'double ( METHOD)(void )' Prototyp, aber er hätte auch als Vorlage verwendet werden können, da Cython mit Vorlagen umgehen kann) :

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

Wo :

  • 'Rückruf' :: Die Pattern/Konverter-Methode zum Auslösen einer Python (=Methode) Objektmethode aus C-typisierten Informationen

  • Methode' :: Die vom Python-Benutzer übergebene effektive Methode (=user_data)

  • 'Parameter' :: Der Parameter, der an die 'Methode' übergeben wird

Jetzt müssen wir die .pyx-Datei implementieren...

Unser Basisprototyp :

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

Dann stellen wir einen Cython-Wrapper für die C++-Klasse zur Verfügung:

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

Die Pattern/Konverter-Methode, die für die Übersetzung des C-typisierten Prototyps in einen Python-Objektaufruf verwendet wird:

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

Nun wollen wir diese Funktionen in eine Cython-Klasse einbetten:

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)

Bearbeiten : Bessere Schreibweise für execute-Funktion : def execute => cpdef double

Das war's. Nennen Sie es so, als ob Sie so etwas tun würden:

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)

Da Python implizit typisiert ist, können wir auf die Eigenschaften des 'obj'-Objekts zugreifen, die mit der Klasse des als Argument übergebenen Objekts zusammenhängen, wenn es an der Zeit ist, den Rückruf auszulösen.

Es kann recht einfach für eine reine C-Implementierung angepasst werden. Bitte sagen Sie mir, wenn Sie eine mögliche Verbesserung für diese sehen (in meinem Fall, perfs sind sehr wichtig, da Ereignisse intensiv gefeuert werden).

5voto

Jan Hudec Punkte 69126

Wenn Sie die Bibliothek ändern können, um zu definieren:

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

Ich fürchte, Sie haben kein Glück. Wenn Sie die void* dann definieren Sie eine Funktion, die ein aufrufbares Python-Objekt mit den richtigen Argumenten aufruft und SetCallBack mit dieser Funktion und dem Python Callable.

Wenn dies nicht möglich ist, der Rückruf aber global ist (was der Fall zu sein scheint), können Sie eine globale Variable erstellen, in der das Python-Objekt gespeichert wird. Dann würden Sie wieder eine Funktion erstellen, die das Python-Objekt aufruft und es an SetCallBack und Ihr PySetCallback würde nur die globale Funktion festlegen und sicherstellen, dass die richtige Funktion registriert wird.

Wenn der Callback kontextspezifisch ist, Sie aber keine Möglichkeit haben, ihm einen "Benutzerdaten"-Zeiger zu übergeben, fürchte ich, dass Sie hier kein Glück haben.

Ich kenne Python und C++, aber nicht Cython, daher weiß ich nicht, ob man die Funktion in Cython erstellen kann oder ob man sie in C++ schreiben müsste.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X