使用 SWIG 包装对象从 C++ 调用 Python 函数的最简洁方法是啥
Posted
技术标签:
【中文标题】使用 SWIG 包装对象从 C++ 调用 Python 函数的最简洁方法是啥【英文标题】:What Is The Cleanest Way to Call A Python Function From C++ with a SWIG Wrapped Object使用 SWIG 包装对象从 C++ 调用 Python 函数的最简洁方法是什么 【发布时间】:2012-09-12 16:34:13 【问题描述】:我有以下代码,它使用 Python 回调函数实现了一个简单的 C++ 类 (ObjWithPyCallback)。这个想法是用“this”作为单个参数来调用 Python 函数。
问题在于,由于 ObjWithPyCallback 是一个 SWIG 包装的对象,我需要 SWIG 类型信息才能创建一个 Python 对象。
问题在于它位于 SWIG 生成的文件“ObjWithPyCallback_wrap.cxx”中。 SWIG 可以生成头文件吗?到目前为止,我还无法做到这一点。
但是,即使使用头文件,SWIG 和我的主要实现之间也存在循环依赖关系,这很烦人。如果可能的话,我想找到一种方法来避免它。最终,ObjWithPyCallback 会在与 Python 绑定不同的共享库中结束。
有没有一种干净的方法来解决这个问题?我知道this post,但它只解决了 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"
【问题讨论】:
我已经通过继承(在 Python 中创建的派生类中使用 Python 版本覆盖的 C++ 虚函数)完成了这项工作,但是直接提供了类似的回调函数。 C++ ***.com/questions/3302146/… 【参考方案1】:以下是我解决此问题的有效解决方案。它使用了上述@omnifarious 和@flexo 的建议。
特别是我们创建了一个带有 SWIG 控制器的回调类,然后在 Python 中从它派生以获得所需的回调功能,而不引入循环依赖。
此外,我们提供了一个接口,它允许任何可调用的 Python 对象充当回调。我们通过使用 SWIG 中的“pythonpred”指令将一些代码添加到“setCallback”函数来实现这一点。此代码仅检查可调用对象,如果找到,则将其包装在 Callback 实例中。
最后,我们处理与让 C++ 类 (ObjWithPyCallback) 引用导演对象(即 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"
文件回调.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);
【讨论】:
ObjWithPyCallback.i 中有一个错误,你有“elif lens(args)”,应该是“elif len(args)”。否则它工作正常。 我不明白什么是“args”。那是你的 swig python 生成器生成的数据吗? Swig 3.0.10 版不会生成这样的变量。 @Jonathan:请注意这是否在 SWIG 中发生了变化。对我有用的是在接口文件中使用以下内容: %feature("shadow") ObjWithPyCallback::setCallback(Callback&) % def setCallback(self, *args): # the code in the original answer $action(self , args[0]) % 它很可能已经改变了。这是大约5年前的事情。我也不再使用这项技术了。如果您有更好/更现代的方法,请发布它,我会更新答案。【参考方案2】:1。解决问题的总体思路:
(1)。定义一个名为 Callback 的 C++ 类,它有一个方法 run()。
(2)。在 Python 代码中继承 Callback,并创建一个实例。
(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)。例子.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 shell中使用
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
【讨论】:
【参考方案3】:我会使用 SWIG 的机制来处理继承,并有一个带有虚函数 void call()
的回调类。然后使用 SWIG 使该类能够在 Python 中派生。
在 Python 中,您只需确保设置回调的位置,将其包装在派生自 C++ 回调类的 Python 类的实例中,并使其call
成员函数执行回调。这也是您进行测试以查看它是否可调用的地方。然后你会用这个包装对象调用setCallback
函数。
【讨论】:
我需要一个回调类的导演,对吧?这似乎可以干净地工作。我会试一试,然后回复你。谢谢! @CraigWright:我已经很久没有做过了(比如 9 年左右),我有点忘记了我到底做了什么。我只知道这是可能的,因为我记得做过一次。 :-) @CraigWright - my answer to another question 的第一部分涵盖了这一点。不过,您可以放心地忽略其余部分。 我的同事使用我们的“真实代码”成功地遵循了您的建议。我将完成上面的示例并将其发布。再次感谢。 @CraigWright:不客气,尽管 Flexo 也值得称赞。 Flexo 指出的答案是一个很好的使用导演功能的教程。以上是关于使用 SWIG 包装对象从 C++ 调用 Python 函数的最简洁方法是啥的主要内容,如果未能解决你的问题,请参考以下文章
SWIG:如何将 C++ 对象数组从 C# 传递到 C++?
使用 SWIG 为 Python 包装 C++。 “向量”未声明