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

use*_*527 8 c++ python swig

我正在将一个用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_使用函数指针/成员函数技巧公开该成员.