Каков самый чистый способ вызова функции Python из С++ с помощью SWIG Wrapped Object
У меня есть следующий код, который реализует простой класс С++ (ObjWithPyCallback) с функцией обратного вызова Python. Идея состоит в том, чтобы вызвать функцию Python с помощью "this" в качестве единственного аргумента.
Проблема в том, что поскольку ObjWithPyCallback - это обернутый SWIG-объект, мне нужен тип SWIG для создания объекта Python.
Проблема заключается в том, что он находится внутри созданного 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"
Ответы
Ответ 1
Ниже мое рабочее решение для решения этой проблемы. Он использует предложения как от @omnifarious, так и от @flexo выше.
В частности, мы создаем класс Callback с директором SWIG, а затем извлекаем его из Python для получения требуемой функции обратного вызова без введения циклической зависимости.
Кроме того, мы предоставляем интерфейс, который позволяет любому объекту Python, вызываемому, выступать в качестве обратного вызова. Мы достигаем этого, используя директиву "pythonprend" в SWIG для добавления некоторого кода в функцию "setCallback". Этот код просто проверяет вызываемый объект, и если он находит его, он обертывает его в экземпляр обратного вызова.
Наконец, мы имеем дело с проблемами памяти, связанными с тем, что ссылка на С++ (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);
}
}
Ответ 2
Я бы использовал механизмы SWIG для обработки наследования и имел класс обратного вызова с виртуальной функцией void call()
. Затем вы используете SWIG для включения этого класса в Python.
В Python вы просто убедитесь, что, когда установлен обратный вызов, вы завершаете его в экземпляр класса Python, полученный из класса обратного вызова С++, и делаете его call
функцией-членом, выполняющей обратный вызов. Это также, где вы бы сделали тест, чтобы узнать, является ли он вызываемым. Затем вы вызываете функцию setCallback
с помощью этого объекта-оболочки.
Ответ 3
1. Общая идея решения проблемы:
(1). Определите класс С++ с именем Callback, который имеет метод run().
(2). Наследовать обратный вызов в коде Python и создать экземпляр.
(3). Используйте метод С++ для привязки экземпляра к С++-указателю.
(4). Используйте pointor для доступа к run(), который определен в коде python.
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