16

次のコードがあります。これは、Pythonコールバック関数を使用して単純なC ++クラス(ObjWithPyCallback)を実装します。アイデアは、「this」を単一の引数としてPython関数を呼び出すことです。

問題は、ObjWithPyCallbackがSWIGでラップされたオブジェクトであるため、Pythonオブジェクトを作成するためにSWIGtypeinfoが必要なことです。

これに伴う問題は、SWIGで生成されたファイル「ObjWithPyCallback_wrap.cxx」内にあることです。SWIGはヘッダーファイルを生成できますか?私はこれまでこれを実現することができませんでした。

ただし、ヘッダーファイルを使用しても、SWIGとメインの実装の間には循環依存関係があり、これは煩わしいことです。可能な限り回避する方法を見つけたいと思います。最終的に、ObjWithPyCallbackは、Pythonバインディングとは異なる共有ライブラリになります。

これをやってのけるためのきれいな方法はありますか?私はこの投稿を知っていますが、SWIG_NewPointerObjの仕組みのみを扱っています。

助けてくれてありがとう!

コードは次のとおりです。

ファイル:example.py

import cb

def foo(x=None):
    print("Hello from Foo!")
    # I'd like x to be a reference to a ObjWithPyCallback object.
    print(x)

o = cb.ObjWithPyCallback()
o.setCallback(foo)
o.call()

ファイル:ObjWithPyCallback.h

#include <Python.h>

class ObjWithPyCallback 
{
   public:

      ObjWithPyCallback();
      void setCallback(PyObject *callback);
      void call();

      PyObject *callback_;
};

ファイル:ObjWithCallback.cpp

#include "ObjWithPyCallback.h"

#include <iostream>

ObjWithPyCallback::ObjWithPyCallback() : callback_(NULL) {}

void ObjWithPyCallback::setCallback(PyObject* callback)
{
   if (!PyCallable_Check(callback))
   {
      std::cerr << "Object is not callable.\n";
   }
   else
   {
      if ( callback_ ) Py_XDECREF(callback_);
      callback_ = callback;
      Py_XINCREF(callback_);
   }
}

void ObjWithPyCallback::call()
{
   if ( ! callback_ )
   {
      std::cerr << "No callback is set.\n";
   }
   else
   {
      // I want to call "callback_(*this)", how to do this cleanly?
      PyObject *result = PyObject_CallFunction(callback_, "");
      if (result == NULL)
         std::cerr << "Callback call failed.\n";
      else
         Py_DECREF(result);
   }
}

ファイル::ObjWithPyCallback.i

%module cb
%{
   #include "ObjWithPyCallback.h"
%}

%include "ObjWithPyCallback.h"
4

3 に答える 3

13

以下は、この問題を解決するための私の実用的な解決策です。上記の@omnifariousと@flexoの両方からの提案を使用します。

特に、SWIGディレクターを使用してコールバッククラスを作成し、Pythonでそれを派生させて、循環依存を導入せずに必要なコールバック機能を取得します。

さらに、呼び出し可能なPythonオブジェクトがコールバックとして機能できるようにするインターフェースを提供します。これを実現するには、SWIGの「pythonprend」ディレクティブを使用して、「setCallback」関数の前にコードを追加します。このコードは、呼び出し可能なオブジェクトをチェックし、見つかった場合は、コールバックのインスタンスでラップします。

最後に、C ++クラス(ObjWithPyCallback)がdirectorオブジェクト(つまり、Callbackのサブクラス)を参照することに関連するメモリの問題に対処します。

ファイルexample.py:

import cb

class CB(cb.Callback):
    def __init__(self):
        super(CB, self).__init__()
    def call(self, x):
        print("Hello from CB!")
        print(x)

def foo(x):
    print("Hello from foo!")
    print(x)

class Bar:
    def __call__(self, x):
        print("Hello from Bar!")
        print(x)


o = cb.ObjWithPyCallback()
mycb=CB()
o.setCallback(mycb)
o.call()
o.setCallback(foo)
o.call()
o.setCallback(Bar())
o.call()

ファイルObjWithPyCallback.i:

%module(directors="1") cb
%{
   #include "Callback.h"
   #include "ObjWithPyCallback.h"
%}
%feature("director") Callback;
%feature("nodirector") ObjWithPyCallback;

%feature("pythonprepend") ObjWithPyCallback::setCallback(Callback&) %{
   if len(args) == 1 and (not isinstance(args[0], Callback) and callable(args[0])):
      class CallableWrapper(Callback):
         def __init__(self, f):
            super(CallableWrapper, self).__init__()
            self.f_ = f
         def call(self, obj):
            self.f_(obj)

      args = tuple([CallableWrapper(args[0])])
      args[0].__disown__()
   elif len(args) == 1 and isinstance(args[0], Callback):
      args[0].__disown__()


%}

%include "Callback.h"
%include "ObjWithPyCallback.h"

ファイルCallback.h:

#ifndef CALLBACK_H
#define CALLBACK_H

class ObjWithPyCallback;

class Callback
{
   public:
      Callback(){}

      virtual ~Callback(){}
      virtual void call(ObjWithPyCallback& object){} 
};

#endif

ファイルObjWithPyCallback.h:

#ifndef OBJWITHPYCALLBACK_H
#define OBJWITHPYCALLBACK_H

class Callback;

class ObjWithPyCallback 
{
   public:

      ObjWithPyCallback();
      ~ObjWithPyCallback();
      void setCallback(Callback &callback);
      void call();

   private:

      Callback* callback_;
};

#endif

ファイルObjWithPyCallback.cpp:

#include "ObjWithPyCallback.h"
#include "Callback.h"

#include <iostream>

ObjWithPyCallback::ObjWithPyCallback() : callback_(NULL) {}

ObjWithPyCallback::~ObjWithPyCallback()
{
}

void ObjWithPyCallback::setCallback(Callback &callback)
{
   callback_ = &callback;
}

void ObjWithPyCallback::call()
{
   if ( ! callback_ )
   {
      std::cerr << "No callback is set.\n";
   }
   else
   {
      callback_->call(*this);
   }
}
于 2012-09-17T16:55:29.777 に答える
7

1.問題を解決するための一般的な考え方:

(1)。run()メソッドを持つCallbackという名前のC++クラスを定義します。

(2)。Pythonコードでコールバックを継承し、インスタンスを作成します。

(3)。C ++メソッドを使用して、インスタンスをC++ポインターにバインドします。

(4)。ポインターを使用して、Pythonコードで定義されているrun()にアクセスします。

2.サンプルコード

(1)。example.h

class Callback{
    public:
    virtual void run(int n);                                                                                                                                                      
    virtual ~Callback() {}; 
};   
extern Callback * callback;
extern void doSomeWithCallback();
extern void setCallback(Callback * cb);

(2)。example.cxx

#include <iostream>
#include "example.h"

int n=0;
Callback * callback = NULL;

void Callback::run(int n){ 
    std::cout << "This print from C++: n = " << n << std::endl;
}    

void setCallback(Callback * cb){
    callback = cb; 
}    

void doSomeWithCallback(){
    if(callback == NULL){
        std::cout << "Must set callback first!" << std::endl;
    }else{
        callback->run(n++);
    }                                                                                                                                                                                         
}

(3)。example.i

/* File : example.i */                                                                                                                                                                        
%module(directors="1") example
%{
#include "example.h"                                                                                                                                                                          
%}

/* turn on director wrapping Callback */
%feature("director") Callback;

%include "example.h"

3.コンパイル

$ swig -c++ -python example.i
$ g++ -c -fPIC example.cxx example_wrap.cxx -I/usr/include/python2.7/
$ g++ -shared example.o example_wrap.o -o _example.so

4.Pythonシェルで使用する

In [1]: import example

In [2]: example.doSomeWithCallback()
Must set callback first!

In [3]: callback = example.Callback()

In [4]: example.setCallback(callback)

In [5]: example.doSomeWithCallback()
This print from C++: n = 0

In [6]: class Callback(example.Callback):
   ...:     def run(self, n):
   ...:         print 'This print from Python: n =', n
   ...:         

In [7]: callback =  Callback()

In [8]: example.setCallback(callback)

In [9]: example.doSomeWithCallback()
This print from Python: n = 1

5.その他

必要なものはもっとあると思います。試す:

$ ls swig-x.x.x/Examples/python
于 2016-05-11T07:22:55.250 に答える
4

継承を処理するためにSWIGのメカニズムを使用し、仮想関数を持つコールバッククラスを作成しますvoid call()。次に、SWIGを使用して、そのクラスをPythonで派生できるようにします。

Pythonでは、コールバックが設定されている場所で、C ++コールバッククラスから派生したPythonクラスのインスタンスでそれをラップし、そのcallメンバー関数にコールバックを実行させるだけです。ここで、呼び出し可能かどうかを確認するためのテストを実行します。setCallback次に、このラッパーオブジェクトを使用して関数を呼び出します。

于 2012-09-12T20:45:58.463 に答える