2012-07-17 10 views
6

estoy envolviendo una biblioteca que fue escrito en C++ para Python API libwebqqC++ back-end llamar el nivel pitón devoluciones de llamada definidos con la envoltura trago

Hay un tipo que se define en función de refuerzo.

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

El nivel de Python puede definir devoluciones de variables de "EventListener".

También hay una estructura de mapa en el nivel C++ que es event_map en la clase Adapter. El tipo de clave de event_map es un tipo de enumeración de QQEvent y el tipo de valor de event_map es la clase "Acción" que envuelve EvenListener.

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" en el adaptador de clase es el API para registrar una función de devolución de llamada de evento relacionado. Y el back-end de C++ lo llamará si sucedió un evento. Pero tenemos que implementar las devoluciones de llamada en el nivel de Python. Y me envolvió el tipo de devolución de llamada en "callback.i"

El problema es que cuando llamo al register_event en Python prueba script, un error de tipo siempre ocurre:

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 

Por favor, ayuda a averiguar la raíz causa de este tipo de error y una solución a este problema.

+0

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

Respuesta

9

El problema parece ser que no ha incluido ningún código para mapear desde un Python llamable a su clase EventListener. No se proporciona de forma gratuita, aunque es algo que aparece bastante regularmente, p. here que sirvió de referencia para parte de esta respuesta.

Su pregunta ha un buen montón de código que no es realmente relevante para el problema y no del todo completa, ya sea por lo que he producido un fichero de cabecera mínima para demostrar el problema y la solución con:

#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"); 
} 

Teniendo en cuenta que archivo de cabecera de un contenedor simple sería:

%module test 

%{ 
#include "test.hh" 
%} 

%include "test.hh" 

también puse juntos un poco de Python para ejecutar esto con:

import test 

def f(x): 
    print(x) 

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

Con esto puedo reproducir el error que se ve:

 
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 &' 

suerte que estamos en la misma página ahora. Hay una única versión de register_handler que espera un objeto del tipo EventListener (el proxy generado por SWIG para el tipo es preciso). No estamos tratando de pasar un EventListener cuando llamamos a esa función, es un Python Callable en su lugar, con poco conocido en el lado de C++, ciertamente no es una coincidencia de tipo o implícitamente convertible. Entonces, necesitamos agregar algo de pegamento en nuestra interfaz para mezclar el tipo de Python en el tipo real de C++.

Hacemos eso definiendo un tipo completamente nuevo, que solo existe internamente en el código de envoltura de SWIG (es decir, dentro de %{ }%). El tipo PyCallback tiene un propósito: mantener una referencia a la cosa real de Python con la que estamos trabajando y hacer que se vea/sienta como una función en C++.

Una vez que hemos añadido PyCallback detalle de implementación (que nadie llega a ver) que se necesita añadir otra sobrecarga para register_handler, que toma un PyObject* directa y construye el PyCallback + EventListener para nosotros. Como esto solo existe con el fin de envolver, usamos %inline para declarar, definir y ajustar todo dentro del archivo de interfaz SWIG. Así que nuestro archivo de interfaz ahora se ve como:

%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)); 
    } 
%} 

En este punto, ahora tenemos suficiente para que el pitón de ensayo original para funcionar correctamente.

Vale la pena señalar que podríamos haber optado por ocultar la sobrecarga original de register_handler, pero en este caso prefiero no hacerlo, es más una característica que un error dejarlo visible porque le permite manipular las devoluciones de llamadas definidas en C++ también, por ejemplo podrías obtenerlos/configurarlos desde el lado de Python y exponer algunos núcleos como variables globales.

En su código real que usted querrá hacer:

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

sobrecargar Action::setCallback, después de la línea %include "test.hh", en lugar de la %inline que utilicé en mi ejemplo a sobrecargar la función gratuita.

Finalmente es posible que desee para exponer operator() de la clase Action usando %rename, o usted podría elegir para exponer el miembro de callback_ utilizando el function pointer/member function trick.

+0

Puño de todos , gracias por su amable apoyo. Agregué la clase Pycallback y "extendí Acción -> setCallback" en el archivo "libwebqq.i", y también cambié "inline register_event_handler" para "extender Adaptador" porque el "register_event_handler" es un método de Adapter. El proyecto puede construir con éxito. Pero cuando el nivel de C++ llama a los callabcks definidos por Python, ocurrirá una falla de segmento. El mensaje de error es "swig/python detectó una pérdida de memoria de tipo 'EventListener *', no se encontró un destructor". – user1530527

+0

Consulte el proyecto libwebqq (https://github.com/gtkqq/libwebqq) – user1530527

+0

@ user1530527 - eso suena como varios problemas. En primer lugar, debe asegurarse de que cuando SWIG ajusta un tipo tenga una definición de la clase suficiente para poder llamar legalmente a 'eliminar'. En segundo lugar, es posible que tenga problemas de bloqueo si su código es de subprocesos múltiples. – Flexo

Cuestiones relacionadas