我正在将一个用C ++编写的库包装到Python API中 libwebqq
有一种在boost函数中定义的类型。
typedef boost::function<void (std::string)> EventListener;
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);
};
类中的“register_event_handler”适配器是用于向相关事件注册回调函数的API。
如果事件发生,C ++后端会调用它。但是我们需要在python级别实现回调。我将回调类型包装在“callback.i”中
问题是,当我在测试python script中调用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
请帮助找出此类型错误的根本原因以及此问题的解决方案。
答案 0 :(得分:11)
问题似乎是你没有包含任何代码来从Python callable映射到你的EventListener
类。它不是免费提供的,虽然它是经常出现的,例如: here作为此答案的一部分的参考。
你的问题有很多代码与问题并不完全相关,也不完全,所以我已经制作了一个最小的头文件来演示问题和解决方案:
#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");
}
鉴于头文件是一个简单的包装器:
%module test
%{
#include "test.hh"
%}
%include "test.hh"
我还把一些Python用来运行它:
import test
def f(x):
print(x)
test.register_handler(test.THING, f)
test.test(test.THING)
有了这个,我可以重现你看到的错误:
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*
并构造{ {1}} + PyCallback
对我们来说。由于这仅用于包装目的,因此我们使用EventListener
来声明,定义和包装SWIG接口文件中的所有内容。所以我们的界面文件现在看起来像:
%inline
此时我们已经有足够的原始测试Python成功运行。
值得注意的是,我们可以选择隐藏%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));
}
%}
的原始重载,但在这种情况下我不喜欢 - 它更像是一个功能,而不是一个错误,让它可见,因为它允许你操纵C ++也定义了回调,例如你可以从Python端获取/设置它们,并将一些核心事件暴露为全局变量。
在您的实际代码中,您需要执行以下操作:
register_handler
在行%extend Action {
void setCallback(PyObject *callback) {
$self->setCallback(PyCallback(callback));
}
}
之后重载Action::setCallback
,而不是我在我的示例中使用的%include "test.hh"
来重载自由函数。
最后,您可能希望使用%inline
公开operator()
课程的Action
,或者您可以选择使用function pointer/member function trick公开%rename
成员。