2012-07-17 2 views
6

ich eine Bibliothek bin Verpackung, die in C geschrieben wurde ++ zu Python API libwebqqC++ zurück, um die Python-Ebene definiert Rückrufe mit swig Wrapper Aufleg

Es ist eine Art, die in Boost-Funktion definiert ist.

Python-Ebene kann variable Callbacks "EventListener" definieren.

Es gibt auch eine Map-Struktur in C++ - Ebene, die in der Klasse Adapter event_map ist. Der Schlüsseltyp von event_map ist ein QQEvent-Enum-Typ und der Werttyp von event_map ist die Klasse "Action", die EvenListener umschließt.

class Action 
{ 

    EventListener _callback; 

    public: 
    Action(){ 
     n_actions++; 
    } 

    Action(const EventListener & cb){ 
    setCallback(cb); 
    } 

    virtual void operator()(std::string data) { 
    _callback(data); 
    } 
    void setCallback(const EventListener & cb){ 
     _callback = cb; 
    } 

    virtual ~Action(){ std::cout<<"Destruct Action"<<std::endl; n_actions --; } 
    static int n_actions; 
}; 


class Adapter{ 

    std::map<QQEvent, Action > event_map; 

public: 
    Adapter(); 
    ~Adapter(); 
    void trigger(const QQEvent &event, const std::string data); 
    void register_event_handler(QQEvent event, EventListener callback); 
    bool is_event_registered(const QQEvent & event); 
    void delete_event_handler(QQEvent event); 
}; 

"register_event_handler" in der Klasse Adapter ist die API eine Rückruffunktion zugehöriges Ereignis zu registrieren. Und C++ Back-End wird es aufrufen, wenn Ereignis passiert ist. Aber wir müssen die Callbacks auf Python-Ebene implementieren. Und ich wickelte den Callback-Typen in „callback.i“

Das Problem ist, wenn ich die register_event in Test Python script nenne, immer eine Art Fehler auftritt:

Traceback (most recent call last): 
File "testwrapper.py", line 44, in <module> 
worker = Worker() 
File "testwrapper.py", line 28, in __init__ 
a.setCallback(self.on_message) 
File "/home/devil/linux/libwebqq/wrappers/python/libwebqqpython.py", line 95, in setCallback 
def setCallback(self, *args): return _libwebqqpython.Action_setCallback(self, *args) 
TypeError: in method 'Action_setCallback', argument 2 of type 'EventListener const &' 
Destruct Action 

Bitte helfen, die Wurzel, um herauszufinden, Ursache für diesen Typ Fehler und eine Lösung für dieses Problem.

+0

http://stackoverflow.com/questions/12392703/what-is-the-cleanest-way-to-call-a-python-function-from-c-with-a-swig-wrapped – xmduhan

Antwort

9

Das Problem scheint zu sein, dass Sie keinen Code enthalten haben, um von einem aufrufbaren Python zu Ihrer EventListener Klasse zuzuordnen. Es ist nicht kostenlos zur Verfügung gestellt, obwohl es etwas ziemlich regelmäßig kommt, z. here, die als Referenz für einen Teil dieser Antwort diente.

Ihre Frage hat ziemlich viel Code, der das Problem nicht wirklich relevant ist und nicht ganz vollständig entweder so habe ich eine minimale Header-Datei erzeugt das Problem und Lösung mit demonstrieren:

#include <boost/function.hpp> 
#include <string> 
#include <map> 

typedef boost::function<void (std::string)> EventListener; 

enum QQEvent { THING }; 

inline std::map<QQEvent, EventListener>& table() { 
    static std::map<QQEvent, EventListener> map; 
    return map; 
} 

inline const EventListener& register_handler(const QQEvent& e, const EventListener& l) { 
    return table()[e] = l; 
} 

inline void test(const QQEvent& e) { 
    table()[e]("Testing"); 
} 

Da Header-Datei eine einfache Hülle wäre:

%module test 

%{ 
#include "test.hh" 
%} 

%include "test.hh" 

ich ein bisschen Python auch diese zusammen laufen mit:

import test 

def f(x): 
    print(x) 

test.register_handler(test.THING, f) 
test.test(test.THING) 

Damit kann ich den Fehler reproduzieren Sie sehen:

 
LD_LIBRARY_PATH=. python3.1 run.py 
Traceback (most recent call last): 
    File "run.py", line 6, in 
    test.register_handler(test.THING, f) 
TypeError: in method 'register_handler', argument 2 of type 'EventListener const &' 

jetzt Hoffentlich sind wir auf der gleichen Seite. Es gibt eine einzige Version von register_handler, die ein Objekt vom Typ EventListener erwartet (SWIGs generierter Proxy für den Typ, um genau zu sein). Wir versuchen nicht, eine EventListener in, wenn wir diese Funktion obwohl - es ist ein Python Callable statt, mit nicht viel auf der C++ - Seite bekannt, sicherlich kein Typ übereinstimmen oder implizit konvertierbar. Also müssen wir etwas Kleber in unsere Schnittstelle einfügen, um den Python-Typ in den echten C++ - Typ zu überführen.

Wir tun das, indem Sie einen völlig neuen Typ definieren, der nur intern für den SWIG-Wrappercode existiert (d. H. Innerhalb von %{ }%). Der Typ PyCallback hat einen Zweck: Halten Sie einen Verweis auf das echte Python-Ding, mit dem wir arbeiten, und lassen Sie es wie eine Funktion in C++ aussehen/fühlen.

Sobald wir hinzugefügt haben, dass PyCallback Implementierung Detail (die niemand zu sehen bekommt) müssen wir dann für register_handler eine weitere Überlastung hinzufügen, die direkt zu einem PyObject* nimmt und baut die PyCallback + EventListener für uns. Da dies nur für den Zweck des Wrappings existiert, verwenden wir %inline, um alle innerhalb der SWIG-Schnittstellendatei zu deklarieren, zu definieren und zu verpacken. Also unsere Interface-Datei sieht nun wie:

%module test 

%{ 
#include "test.hh" 

class PyCallback 
{ 
    PyObject *func; 
    PyCallback& operator=(const PyCallback&); // Not allowed 
public: 
    PyCallback(const PyCallback& o) : func(o.func) { 
     Py_XINCREF(func); 
    } 
    PyCallback(PyObject *func) : func(func) { 
     Py_XINCREF(this->func); 
     assert(PyCallable_Check(this->func)); 
    } 
    ~PyCallback() { 
     Py_XDECREF(func); 
    } 
    void operator()(const std::string& s) { 
     if (!func || Py_None == func || !PyCallable_Check(func)) 
     return; 
     PyObject *args = Py_BuildValue("(s)", s.c_str()); 
     PyObject *result = PyObject_Call(func,args,0); 
     Py_DECREF(args); 
     Py_XDECREF(result); 
    } 
}; 
%} 

%include "test.hh" 

%inline %{ 
    void register_handler(const QQEvent& e, PyObject *callback) { 
    register_handler(e, PyCallback(callback)); 
    } 
%} 

An dieser Stelle wir jetzt genug für den Original-Test-Python erfolgreich ausgeführt werden.

Es ist erwähnenswert, dass wir die ursprüngliche Überladung von verstecken könnten, aber in diesem Fall bevorzuge ich nicht - es ist mehr eine Eigenschaft als ein Fehler, diese sichtbar zu lassen, weil es Ihnen erlaubt, C++ definierte Rückrufe zu manipulieren auch, z Sie können sie von der Python-Seite bekommen/setzen und einige als globale Variablen darstellen.

In Ihrem aktuellen Code möchten Sie tun:

%extend Action { 
    void setCallback(PyObject *callback) { 
    $self->setCallback(PyCallback(callback)); 
    } 
} 

zu Action::setCallback überlasten, nach der Zeile %include "test.hh" anstelle des %inline ich in meinem Beispiel verwendet, um die Freisprechfunktion zu überlasten.

Schließlich möchten Sie vielleicht operator() Ihrer Action Klasse aussetzen %rename verwenden, oder Sie können das callback_ Mitglied mit dem function pointer/member function trick aussetzen wollte.

+0

Faust von allen , Danke für deine freundliche Unterstützung. Ich habe die Pycallback-Klasse und "extend Action -> setCallback" in der Datei "libwebqq.i" hinzugefügt und auch "inline register_event_handler" in "extend Adapter" geändert, weil "register_event_handler" eine Methode von Adapter ist. Das Projekt kann erfolgreich erstellt werden. Wenn die C++ - Ebene jedoch die Python-definierten Callabcks aufruft, tritt ein Segmentfehler auf. Die Fehlermeldung lautet "swig/python hat einen Speicherverlust vom Typ 'EventListener *' gefunden, kein Destruktor gefunden." – user1530527

+0

Bitte beziehen Sie sich auf das Projekt libwebqq (https://github.com/gtkqq/libwebqq) – user1530527

+0

@ user1530527 - das klingt nach mehreren Problemen. Zuerst müssen Sie sicherstellen, dass wenn SWIG einen Typ umschließt, eine Definition der Klasse ausreicht, um legal 'delete' aufrufen zu können. Zweitens haben Sie möglicherweise Sperrprobleme, wenn Ihr Code Multi-Threading ist. – Flexo

Verwandte Themen