Каков самый чистый способ вызова функции Python из C++ с завернутым объектом SWIG
у меня есть следующий код, который реализует простой класс C++ (ObjWithPyCallback) с функцией обратного вызова Python. Идея состоит в том, чтобы вызвать функцию Python с "this" в качестве единственного аргумента.
проблема в том, что поскольку ObjWithPyCallback-это объект, обернутый SWIG, мне нужен SWIG typeinfo для создания объекта Python.
проблема в том, что оно внутри СВИГ созданный файл "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);
   }
}
File:: ObjWithPyCallback.я!--5-->
%module cb
%{
   #include "ObjWithPyCallback.h"
%}
%include "ObjWithPyCallback.h"
            3 ответов
ниже мое рабочее решение для решения этой проблемы. Он использует предложения от @omnifarious и @flexo выше.
в частности, мы создаем класс обратного вызова с директором SWIG, а затем производным от него в Python, чтобы получить необходимую функциональность обратного вызова без введения круговой зависимости.
кроме того, мы предоставляем интерфейс, который позволяет любому объекту Python, который вызывается, действовать как обратный вызов. Мы достигаем этого, используя " pythonprend" директива в SWIG, чтобы добавить некоторый код к функции "setCallback". Этот код просто проверяет вызываемый объект, и если он его находит, обертывает его в экземпляр обратного вызова.
наконец, мы имеем дело с проблемами памяти, связанными с тем, что класс C++ (ObjWithPyCallback) ссылается на объект director (т. е. подкласс обратного вызова).
файл 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.я:
%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"
 Обратный звонок.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);
   }
}
Я бы использовал механизмы SWIG для обработки наследования и имел класс обратного вызова с виртуальной функцией void call(). Затем вы используете SWIG, чтобы этот класс был производным от Python.
в Python вы просто убедитесь, что там, где установлен обратный вызов, вы обертываете его в экземпляр класса Python, производного от класса обратного вызова C++, и делаете это call функция-член выполняет обратный вызов. Это также место, где вы бы сделали тест, чтобы узнать, можно ли его вызвать. Тогда бы ты позвонила. the 
1. Общая идея решения задачи:
(1). Определите класс C++ с именем Callback, который имеет метод run ().
(2). Наследовать обратный вызов в коде Python и создать экземпляр.
(3). Используйте метод C++ для привязки экземпляра к указателю C++.
(4). Используйте указатель для доступа к run (), который определен в коде python.
2. Пример кода
(1). образец.h
class Callback{
    public:
    virtual void run(int n);                                                                                                                                                      
    virtual ~Callback() {}; 
};   
extern Callback * callback;
extern void doSomeWithCallback();
extern void setCallback(Callback * cb);
(2). образец.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). образец.я!--9-->
/* File : example.i */                                                                                                                                                                        
%module(directors="1") example
%{
#include "example.h"                                                                                                                                                                          
%}
/* turn on director wrapping Callback */
%feature("director") Callback;
%include "example.h"
3. Compile
$ 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. Другое
/* File : example.i */                                                                                                                                                                        
%module(directors="1") example
%{
#include "example.h"                                                                                                                                                                          
%}
/* turn on director wrapping Callback */
%feature("director") Callback;
%include "example.h"
$ 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
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
я думаю, что вам нужно больше. Попробуйте:
$ ls swig-x.x.x/Examples/python