c ++后端调用python级定义的回调与swig包装 [英] c++ back end call the python level defined callbacks with swig wrapper

查看:226
本文介绍了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;
};


类适配器{

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);
};类Adapter中的

register_event_handler是用于向相关事件注册回调函数的API。
和C ++后端将调用它如果事件发生。但是我们需要在python级别实现回调。我在callback.i中包装回调类型



问题是,当我在test python 脚本,总会发生类型错误:

  Traceback(最近一次调用):
文件testwrapper.py,第44行,在< module>
worker = Worker()
文件testwrapper.py,第28行,在__init__
中a.setCallback(self.on_message)
文件/ home / devil / linux /在setCallback
中的第95行:set setCallback(self,* args):return _libwebqqpython.Action_setCallback(self,* args)
TypeError:在方法'Action_setCallback' ,类型为EventListener const&'
的参数2 Destruct Action

请帮助出了这个类型错误的根本原因和解决这个问题。

解决方案

问题似乎是,你没有包含任何代码从Python可调用映射到 EventListener 类。它不是免费提供,虽然它是相当定期,例如。 此处,作为此答案一部分的参考。 p>

您的问题有相当多的代码,并不是真正相关的问题,不是很完整,所以我已经生成一个最小的头文件来演示问题和解决方案:

  #include< boost / function.hpp> 
#include< string>
#include< map>

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

枚举QQEvent {THING};

inline std :: map< QQEvent,EventListener>& table(){
static std :: map< QQEvent,EventListener>地图;
return map;
}

inline const EventListener& register_handler(const QQEvent& e,const EventListener& l){
return table()[e] = 1;
}

inline void test(const QQEvent& e){
table()[e](Testing);
}

给定的头文件一个简单的包装器将是:

 %模块测试

%{
#includetest.hh
%}

%includetest.hh

用以下命令运行:

  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(最近一次调用):
文件run.py,第6行,在
test.THING,f)
TypeError:在方法'register_handler'中,类型为'EventListener const'的参数2

希望我们现在在同一页。有一个版本的 register_handler ,期望一个类型 EventListener 的对象(SWIG生成的代理类型是精确的) 。当我们调用该函数时,我们并不试图传递一个 EventListener - 它是一个Python Callable,而不是在C ++端知道 - 肯定不是类型匹配或隐式转换。因此,我们需要在我们的界面中添加一些胶水,使Python类型进入真正的C ++类型。



我们通过定义一个全新的类型,它只存在于SWIG包装器代码内部(即%{}%)。类型 PyCallback 有一个目的:保存对我们正在使用的真正Python事物的引用,并使其看起来/感觉像C ++中的函数。



一旦我们添加了 PyCallback 实现细节(没有人可以看到),我们需要为<$ cb添加另一个重载$ c> register_handler ,它直接获取一个 PyObject * ,并构造 PyCallback code> EventListener 为我们。因为这只存在于包装的目的,所以使用%inline 在SWIG接口文件中声明,定义和包装所有。所以我们的界面文件现在看起来像:

 %module test 

%{
# includetest.hh

类PyCallback
{
PyObject * func;
PyCallback& operator =(const PyCallback&); //不允许
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))
;
PyObject * args = Py_BuildValue((s),s.c_str());
PyObject * result = PyObject_Call(func,args,0)
Py_DECREF(args);
Py_XDECREF(result);
}
};
%}

%includetest.hh

%inline%{
void register_handler(const QQEvent& e,PyObject * callback){
register_handler(e,PyCallback(callback));
}
%}

测试Python成功运行。



值得注意的是,我们可以选择隐藏 register_handler 但在这种情况下,我不喜欢 - 它更多的是一个功能,而不是一个错误,让它可见,因为它允许你操纵C ++定义的回调,例如您可以从Python端获取/设置它们,并将一些核心对象作为全局变量。



在您的实际代码中,您将需要:

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

重载%includetest.hh之后,而不是%inline



最后,你可能想要暴露 operator()使用%rename Action 类,或者您可以选择公开 callback _ 成员使用函数指针/成员函数技巧


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:

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

Given that header file a simple wrapper would be:

%module test

%{
#include "test.hh"
%}

%include "test.hh"

I also put together a bit of Python to run this with:

import test

def f(x):
  print(x)

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

With this I can reproduce the error you see:

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

Hopefully we're on the same page now. There's a single version of register_handler that expects an object of type EventListener (SWIG's generated proxy for the type to be precise). We're not trying to pass an EventListener 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 type PyCallback 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 for register_handler, which takes a PyObject* directly and constructs the PyCallback+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:

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

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:

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

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 your Action class using %rename, or you could chose to expose the callback_ member using the function pointer/member function trick.

这篇关于c ++后端调用python级定义的回调与swig包装的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆