c ++后端使用swig包装器调用python级别定义的回调

时间:2012-07-17 06:00:57

标签: c++ python swig

我正在将一个用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

请帮助找出此类型错误的根本原因以及此问题的解决方案。

1 个答案:

答案 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成员。