8

C++ で記述されたライブラリを Python API libwebqqにラップしています

ブースト機能で定義されている型があります。

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

Python レベルでは、「EventListener」変数のコールバックを定義できます。

クラス Adapter の event_map である C++ レベルのマップ構造もあります。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);
};

クラス Adapter の「register_event_handler」は、関連するイベントにコールバック関数を登録するための API です。イベントが発生した場合、C++ バックエンドがそれを呼び出します。しかし、Python レベルでコールバックを実装する必要があります。そして、コールバック タイプを「callback.i」でラップしました。

問題は、テスト 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

このタイプのエラーの根本的な原因と、この問題の解決策を見つけてください。

4

1 に答える 1

12

問題は、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
トレースバック (最新の呼び出しが最後):
  ファイル「run.py」の 6 行目
    test.register_handler(test.THING, f)
TypeError: メソッド 'register_handler' で、タイプ 'EventListener const &' の引数 2

うまくいけば、私たちは今同じページにいます。register_handlerタイプのオブジェクトを期待するの単一バージョンがありますEventListener(タイプを正確にするための SWIG の生成されたプロキシ)。ただし、その関数を呼び出すときに inを渡そうとはしていませんEventListener- 代わりに Python Callable であり、C++ 側ではあまり知られていません - 確かに型の一致や暗黙的な変換はできません。そのため、インターフェイスに接着剤を追加して、Python 型を実際の C++ 型に変換する必要があります。

これは、SWIG ラッパー コードの内部 (つまり 内) にのみ存在する、まったく新しい型を定義することによって行います%{ }%。この型PyCallbackには 1 つの目的があります。使用している実際の Python のものへの参照を保持し、C++ の関数のように見えるようにすることです。

PyCallback実装の詳細 (誰も見ることができません) を追加したら、別のオーバーロードを追加する必要がregister_handlerありPyObject*ますPyCallbackEventListenerこれはラッピングの目的でのみ存在するため%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));
  }
}

この例で free 関数をオーバーロードするために使用したの代わりにAction::setCallback、行の後にをオーバーロードします。%include "test.hh"%inline

最後に、 を使用してクラスを公開するoperator()Action関数ポインター/メンバー関数のトリックを使用してメンバーを%rename公開することを選択できます。callback_

于 2012-07-17T12:41:34.960 に答える