2012-07-17 60 views
6

我包装纸将其用C++编写到Python API libwebqqC++后端调用蟒级别定义回调与痛饮包装

有一个库是设置在升压功能定义的类型。

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

Python级别可以定义“EventListener”变量回调。

还有一个C++级别的map结构,它是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”Adapter是向相关事件注册回调函数的API。 如果发生事件,C++后端会调用它。但是我们需要在python级别实现回调。我裹着“callback.i”回调类型

问题是,当我致电register_event测试蟒蛇script,一个类型的错误总是发生:

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

http://stackoverflow.com/questions/12392703/what-is-the-cleanest-way-to-call-a-python-function-from-c-with-a-swig-wrapped – xmduhan 2016-05-11 10:33:08

回答

9

问题似乎是,您没有包含任何代码,从Python可调用映射到您的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"); 
} 

鉴于头文件一个简单的包装是:

import test 

def f(x): 
    print(x) 

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

%module test 

%{ 
#include "test.hh" 
%} 

%include "test.hh" 

我也把一个位的Python与运行此

有了这个,我可以重现你看到错误:

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

在这一点上,我们现在有足够的原始测试Python运行成功。

值得一提的是,我们可以选择隐藏的register_handler原来的过载,但在这种情况下我不喜欢 - 这是不是一个错误更多功能的离开是可见的,因为它允许你来操纵C++定义回调也,例如您可以从Python端获取/设置它们,并将一些核心文件暴露为全局变量。

在您的实际代码,你会想做的事:

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

超载Action::setCallback,行%include "test.hh"之后,而不是%inline我在我的例子中使用重载的免费功能。

最后,你可能想使用%rename暴露你的Actionoperator(),或者你可以选择使用function pointer/member function trick揭露callback_成员。

+0

所有拳头,谢谢你的支持。我在文件“libwebqq.i”中添加了Pycallback类和“extend Action - > setCallback”,并将“inline register_event_handler”更改为“extend Adapter”,因为“register_event_handler”是Adapter的一种方法。该项目可以成功建立。但是当C++级别调用python定义的callabcks时,会发生段错误。错误消息是“swig/python检测到类型为'EventListener *'的内存泄漏,未找到任何析构函数。” – user1530527 2012-07-18 03:13:10

+0

请参考项目libwebqq(https://github.com/gtkqq/libwebqq) – user1530527 2012-07-18 03:22:00

+0

@ user1530527 - 这听起来像几个问题。首先,你需要确保当SWIG包装一个类型时,它有一个类的定义足以能够合法地调用delete。其次,如果你的代码是多线程的,你可能会遇到锁定问题。 – Flexo 2012-07-19 08:24:18