I am wrapping a library which was written in C++ to Python API libwebqq
There is a type which is defined in boost function .
typedef boost::function<void (std::string)> EventListener;
Python level can define "EventListener" variable callbacks.
There is also a map structure in C++ level which is event_map in class Adapter. The key type of event_map is a QQEvent enum type and the value type of event_map is class "Action" which wraps 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" in class Adapter is the API to register a callback function to related event. And C++ back end will call it if event happened. But we need to implement the callbacks in python level. And I wrapped the callback type in "callback.i"
The problem is , when I call the register_event in test python script, a type error always occurs:
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
Please help to figure out the root cause of this type error and a solution to this problem.
The problem seems to be that you haven't included any code to map from a Python callable to your
EventListener
class. It's not provided for free, although it's something that comes up fairly regularly, e.g. here which acted as a reference for part of this answer.Your question has quite a lot of code that's not really relevant to the problem and not quite complete either so I've produced a minimal header file to demonstrate the problem and solution with:
Given that header file a simple wrapper would be:
I also put together a bit of Python to run this with:
With this I can reproduce the error you see:
Hopefully we're on the same page now. There's a single version of
register_handler
that expects an object of typeEventListener
(SWIG's generated proxy for the type to be precise). We're not trying to pass anEventListener
in when we call that function though - it's a Python Callable instead, with not much known on the C++ side - certainly not a type match or implicitly convertible. So we need to add some glue in our interface to mush the Python type into the real C++ type.We do that by defining an entirely new type, which only exists internally to the SWIG wrapper code (i.e. within
%{ }%
). The typePyCallback
has one purpose: hold a reference to the real Python thing we're working with and make it look/feel like a function in C++.Once we've added that
PyCallback
implementation detail (which nobody gets to see) we then need to add another overload forregister_handler
, which takes aPyObject*
directly and constructs thePyCallback
+EventListener
for us. Since this only exists for the purpose of wrapping we use%inline
to declare, define and wrap all within the SWIG interface file. So our interface file now looks like:At this point we now have enough for the original test Python to run successfully.
It's worth noting that we could have chosen to hide the original overload of
register_handler
, but in this instance I prefer not to - it's more of a feature than a mistake to leave that visible because it permits you to manipulate C++ defined callbacks also, e.g. you could get/set them from the Python side and expose some core ones as global variables.In your actual code you'll want to do:
to overload
Action::setCallback
, after the line%include "test.hh"
, instead of the%inline
I used in my example to overload the free function.Finally you might want to expose
operator()
of yourAction
class using%rename
, or you could chose to expose thecallback_
member using the function pointer/member function trick.