我正在将一个用C++编写的库包装到Python API libwebqq中
有一种在boost函数中定义的类型.
typedef boost::function<void (std::string)> EventListener;
Run Code Online (Sandbox Code Playgroud)
Python级别可以定义"EventListener"变量回调.
C++级别中还有一个映射结构,它是类Adapter中的event_map.event_map的关键类型是QQEvent枚举类型,event_map的值类型是包含EvenListener的类"Action".
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);
};
Run Code Online (Sandbox Code Playgroud)
类Adapter中的"register_event_handler"是用于向相关事件注册回调函数的API.如果事件发生,C++后端会调用它.但是我们需要在python级别实现回调.我将回调类型包装在"callback.i"中
问题是,当我在test python 脚本中调用register_event时,总是会出现类型错误:
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
Run Code Online (Sandbox Code Playgroud)
请帮助找出此类型错误的根本原因以及此问题的解决方案.
Fle*_*exo 12
问题似乎是你没有包含任何代码从Python可调用映射到你的EventListener类.它不是免费提供的,虽然它是经常出现的东西,例如这里作为这个答案的一部分的参考.
你的问题有很多代码与问题并不完全相关,也不完全,所以我已经制作了一个最小的头文件来演示问题和解决方案:
#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");
}
Run Code Online (Sandbox Code Playgroud)
给定头文件一个简单的包装器将是:
%module test
%{
#include "test.hh"
%}
%include "test.hh"
Run Code Online (Sandbox Code Playgroud)
我还把一些Python用来运行它:
import test
def f(x):
print(x)
test.register_handler(test.THING, f)
test.test(test.THING)
Run Code Online (Sandbox Code Playgroud)
有了这个,我可以重现你看到的错误:
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 &'
希望我们现在在同一页上.有一个版本register_handler需要一个类型的对象EventListener(SWIG生成的代理类型是精确的).EventListener当我们调用该函数时,我们并没有尝试传入- 它是一个Python Callable,而在C++方面并不为人所知 - 当然不是类型匹配或隐式可转换.因此,我们需要在界面中添加一些粘合剂,以便将Python类型转换为真正的C++类型.
我们通过定义一个全新类型来实现这一点,该类型仅存在于SWIG包装器代码内部(即内部%{ }%).该类型PyCallback有一个目的:保持对我们正在使用的真实Python事物的引用,并使其看起来像C++中的函数.
一旦我们添加了PyCallback实现细节(没有人看到),我们就需要添加另一个重载register_handler,PyObject*直接接受并为我们构造PyCallback+ EventListener.由于这仅用于包装目的,我们用于%inline在SWIG接口文件中声明,定义和包装所有内容.所以我们的界面文件现在看起来像:
%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));
}
%}
Run Code Online (Sandbox Code Playgroud)
在这一点上,我们现在已经有足够的原始测试Python成功运行.
值得注意的是,我们可以选择隐藏原始的重载register_handler,但是在这种情况下我不喜欢 - 它更像是一个功能而不是错误,因为它允许你操纵C++定义的回调,例如你可以从Python端获取/设置它们并将一些核心事件暴露为全局变量.
在您的实际代码中,您需要执行以下操作:
%extend Action {
void setCallback(PyObject *callback) {
$self->setCallback(PyCallback(callback));
}
}
Run Code Online (Sandbox Code Playgroud)
Action::setCallback在行之后重载,%include "test.hh"而不是%inline我在我的例子中用来重载自由函数.
最后,您可能希望使用类公开operator()您的Action类%rename,或者您可以选择callback_使用函数指针/成员函数技巧公开该成员.