我有一个用C ++编写的类接口。我有一些实现此接口的类也是用C ++编写的。这些是在更大的C ++程序的上下文中调用的,它基本上实现了“main”。我希望能够在Python中编写这个接口的实现,并允许它们在更大的C ++程序的上下文中使用,就好像它们只是用C ++编写的一样。
有很多关于连接python和C ++的文章,但我无法弄清楚如何做我想做的事情。我能找到的最接近的地方是:http://www.cs.brown.edu/~jwicks/boost/libs/python/doc/tutorial/doc/html/python/exposing.html#python.class_virtual_functions,但这不太正确。
更具体地说,假设我现有的C ++接口定义如下:
// myif.h
class myif {
public:
virtual float myfunc(float a);
};
我希望能够做的是:
// mycl.py
... some magic python stuff ...
class MyCl(myif):
def myfunc(a):
return a*2
然后,回到我的C ++代码中,我希望能够说出类似的内容:
// mymain.cc
void main(...) {
... some magic c++ stuff ...
myif c = MyCl(); // get the python class
cout << c.myfunc(5) << endl; // should print 10
}
我希望这很清楚;)
答案 0 :(得分:39)
这个答案分为两个部分。首先,您需要以允许Python实现随意覆盖部分内容的方式在Python中公开您的接口。然后你需要展示你的C ++程序(在main
中如何调用Python。
第一部分很容易用SWIG做。我稍微修改了您的示例场景以修复一些问题,并为测试添加了额外的功能:
// myif.h
class myif {
public:
virtual float myfunc(float a) = 0;
};
inline void runCode(myif *inst) {
std::cout << inst->myfunc(5) << std::endl;
}
现在我将在不在您的应用程序中嵌入Python的情况下查看问题,即您在Python中开始排除,而不是在C ++中的int main()
。稍后添加它是相当简单的。
首先得到cross-language polymorphism working:
%module(directors="1") module
// We need to include myif.h in the SWIG generated C++ file
%{
#include <iostream>
#include "myif.h"
%}
// Enable cross-language polymorphism in the SWIG wrapper.
// It's pretty slow so not enable by default
%feature("director") myif;
// Tell swig to wrap everything in myif.h
%include "myif.h"
为此,我们在全球范围内启用了SWIG的导演功能,特别是我们的界面。其余部分是非常标准的SWIG。
我写了一个测试Python实现:
import module
class MyCl(module.myif):
def __init__(self):
module.myif.__init__(self)
def myfunc(self,a):
return a*2.0
cl = MyCl()
print cl.myfunc(100.0)
module.runCode(cl)
然后我就可以编译并运行它:
swig -python -c++ -Wall myif.i g++ -Wall -Wextra -shared -o _module.so myif_wrap.cxx -I/usr/include/python2.7 -lpython2.7 python mycl.py 200.0 10
您希望从该测试中看到什么。
接下来我们需要实现mymain.cc的真实版本。我已经把它的外观描绘成一幅草图:
#include <iostream>
#include "myif.h"
#include <Python.h>
int main()
{
Py_Initialize();
const double input = 5.0;
PyObject *main = PyImport_AddModule("__main__");
PyObject *dict = PyModule_GetDict(main);
PySys_SetPath(".");
PyObject *module = PyImport_Import(PyString_FromString("mycl"));
PyModule_AddObject(main, "mycl", module);
PyObject *instance = PyRun_String("mycl.MyCl()", Py_eval_input, dict, dict);
PyObject *result = PyObject_CallMethod(instance, "myfunc", (char *)"(O)" ,PyFloat_FromDouble(input));
PyObject *error = PyErr_Occurred();
if (error) {
std::cerr << "Error occured in PyRun_String" << std::endl;
PyErr_Print();
}
double ret = PyFloat_AsDouble(result);
std::cout << ret << std::endl;
Py_Finalize();
return 0;
}
它基本上只是标准embedding Python in another application。它的工作原理也完全符合您的希望:
g++ -Wall -Wextra -I/usr/include/python2.7 main.cc -o main -lpython2.7 ./main 200.0 10 10
最后一个难题是能够将从Python中创建实例获得的PyObject*
转换为myif *
。 SWIG再次使这一点变得相当简单。
首先,我们需要让SWIG在头文件中为我们公开它的运行时。我们通过额外调用SWIG来做到这一点:
swig -Wall -c++ -python -external-runtime runtime.h
接下来我们需要重新编译我们的SWIG模块,明确给出SWIG知道名称的类型表,以便我们可以从main.cc中查找它。我们使用:
重新编译.sog++ -DSWIG_TYPE_TABLE=myif -Wall -Wextra -shared -o _module.so myif_wrap.cxx -I/usr/include/python2.7 -lpython2.7
然后我们添加一个辅助函数,用于将main.cc中的PyObject*
转换为myif*
:
#include "runtime.h"
// runtime.h was generated by SWIG for us with the second call we made
myif *python2interface(PyObject *obj) {
void *argp1 = 0;
swig_type_info * pTypeInfo = SWIG_TypeQuery("myif *");
const int res = SWIG_ConvertPtr(obj, &argp1,pTypeInfo, 0);
if (!SWIG_IsOK(res)) {
abort();
}
return reinterpret_cast<myif*>(argp1);
}
现在我们可以在main()
:
int main()
{
Py_Initialize();
const double input = 5.5;
PySys_SetPath(".");
PyObject *module = PyImport_ImportModule("mycl");
PyObject *cls = PyObject_GetAttrString(module, "MyCl");
PyObject *instance = PyObject_CallFunctionObjArgs(cls, NULL);
myif *inst = python2interface(instance);
std::cout << inst->myfunc(input) << std::endl;
Py_XDECREF(instance);
Py_XDECREF(cls);
Py_Finalize();
return 0;
}
最后,我们必须使用-DSWIG_TYPE_TABLE=myif
编译main.cc,这会给出:
./main 11
答案 1 :(得分:12)
最小的例子;请注意,由于Base
不是纯虚拟的,因此很复杂。我们去了:
baz.cpp:
#include<string>
#include<boost/python.hpp>
using std::string;
namespace py=boost::python;
struct Base{
virtual string foo() const { return "Base.foo"; }
// fooBase is non-virtual, calling it from anywhere (c++ or python)
// will go through c++ dispatch
string fooBase() const { return foo(); }
};
struct BaseWrapper: Base, py::wrapper<Base>{
string foo() const{
// if Base were abstract (non-instantiable in python), then
// there would be only this->get_override("foo")() here
//
// if called on a class which overrides foo in python
if(this->get_override("foo")) return this->get_override("foo")();
// no override in python; happens if Base(Wrapper) is instantiated directly
else return Base::foo();
}
};
BOOST_PYTHON_MODULE(baz){
py::class_<BaseWrapper,boost::noncopyable>("Base")
.def("foo",&Base::foo)
.def("fooBase",&Base::fooBase)
;
}
bar.py
import sys
sys.path.append('.')
import baz
class PyDerived(baz.Base):
def foo(self): return 'PyDerived.foo'
base=baz.Base()
der=PyDerived()
print base.foo(), base.fooBase()
print der.foo(), der.fooBase()
生成文件
default:
g++ -shared -fPIC -o baz.so baz.cpp -lboost_python `pkg-config python --cflags`
结果是:
Base.foo Base.foo
PyDerived.foo PyDerived.foo
您可以在其中查看fooBase()
(非虚拟c ++函数)如何调用虚拟foo()
,无论是在c ++还是python中,它都会解析为覆盖。你可以在C ++中从Base派生一个类,它的工作方式也是一样的。
编辑(提取c ++对象):
PyObject* obj; // given
py::object pyObj(obj); // wrap as boost::python object (cheap)
py::extract<Base> ex(pyObj);
if(ex.check()){ // types are compatible
Base& b=ex(); // get the wrapped object
// ...
} else {
// error
}
// shorter, thrwos when conversion not possible
Base &b=py::extract<Base>(py::object(obj))();
从py::object
构造PyObject*
并使用py::extract
来查询python对象是否与您要提取的内容相匹配:PyObject* obj; py::extract<Base> extractor(py::object(obj)); if(!extractor.check()) /* error */; Base& b=extractor();
答案 2 :(得分:10)
引用http://wiki.python.org/moin/boost.python/Inheritance
“Boost.Python还允许我们表示C ++继承关系,以便可以传递包装的派生类,其中值,指针或对基类的引用被期望作为参数。”
有虚拟函数的例子,因此解决了第一部分(具有类MyCl(myif)的那部分)
对于执行此操作的具体示例,http://wiki.python.org/moin/boost.python/OverridableVirtualFunctions
对于线myif c = MyCl();你需要将你的python(模块)暴露给C ++。这里有一些例子http://wiki.python.org/moin/boost.python/EmbeddingPython
答案 3 :(得分:8)
基于the (very helpful) answer by Eudoxos我已经采用了他的代码并将其扩展为现在有一个嵌入式解释器,内置模块。
这个答案是Boost.Python等价于my SWIG based answer。
headerfile myif.h:
class myif {
public:
virtual float myfunc(float a) const { return 0; }
virtual ~myif() {}
};
基本上和问题一样,但是使用myfunc
的默认实现和虚拟析构函数。
对于Python实现,MyCl.py与问题基本相同:
import myif
class MyCl(myif.myif):
def myfunc(self,a):
return a*2.0
然后离开mymain.cc,其中大部分是基于Eudoxos的回答:
#include <boost/python.hpp>
#include <iostream>
#include "myif.h"
using namespace boost::python;
// This is basically Eudoxos's answer:
struct MyIfWrapper: myif, wrapper<myif>{
float myfunc(float a) const {
if(this->get_override("myfunc"))
return this->get_override("myfunc")(a);
else
return myif::myfunc(a);
}
};
BOOST_PYTHON_MODULE(myif){
class_<MyIfWrapper,boost::noncopyable>("myif")
.def("myfunc",&myif::myfunc)
;
}
// End answer by Eudoxos
int main( int argc, char ** argv ) {
try {
// Tell python that "myif" is a built-in module
PyImport_AppendInittab("myif", initmyif);
// Set up embedded Python interpreter:
Py_Initialize();
object main_module = import("__main__");
object main_namespace = main_module.attr("__dict__");
PySys_SetPath(".");
main_namespace["mycl"] = import("mycl");
// Create the Python object with an eval()
object obj = eval("mycl.MyCl()", main_namespace);
// Find the base C++ type for the Python object (from Eudoxos)
const myif &b=extract<myif>(obj)();
std::cout << b.myfunc(5) << std::endl;
} catch( error_already_set ) {
PyErr_Print();
}
}
我在这里添加的关键部分,超出了“我如何使用Boost.Python嵌入Python?”和“如何使用Boost.python扩展Python?” (这是由Eudoxos回答的)是“如何在同一个程序中同时进行这两个问题?”这个问题的答案。对此的解决方案在于PyImport_AppendInittab
调用,该调用采用初始化函数,该函数通常在加载模块时调用并将其注册为内置模块。因此,当mycl.py说import myif
时,它最终会导入内置的Boost.Python模块。
答案 4 :(得分:1)
看看Boost Python,它是在C ++和Python之间架起桥梁的最通用和最强大的工具。
答案 5 :(得分:-2)
没有真正的方法可以直接使用Python连接C ++代码。
SWIG会处理这个问题,但它会构建自己的包装器。
我更喜欢SWIG的另一种选择是ctypes,但要使用它,你需要创建一个C包装器。
例如:
// myif.h
class myif {
public:
virtual float myfunc(float a);
};
像这样构建一个C包装器:
extern "C" __declspec(dllexport) float myif_myfunc(myif* m, float a) {
return m->myfunc(a);
}
由于您使用C ++构建,因此extern“C”允许C链接,因此您可以从dll轻松调用它,而__declspec(dllexport)允许从dll调用该函数。
在Python中:
from ctypes import *
from os.path import dirname
dlldir = dirname(__file__) # this strips it to the directory only
dlldir.replace( '\\', '\\\\' ) # Replaces \ with \\ in dlldir
lib = cdll.LoadLibrary(dlldir+'\\myif.dll') # Loads from the full path to your module.
# Just an alias for the void pointer for your class
c_myif = c_void_p
# This tells Python how to interpret the return type and arguments
lib.myif_myfunc.argtypes = [ c_myif, c_float ]
lib.myif_myfunc.restype = c_float
class MyCl(myif):
def __init__:
# Assume you wrapped a constructor for myif in C
self.obj = lib.myif_newmyif(None)
def myfunc(a):
return lib.myif_myfunc(self.obj, a)
虽然SWIG为您完成了所有这些工作,但您可以随意修改内容,而不会因重新生成SWIG包装器而需要重做的所有更改感到沮丧。
ctypes的一个问题是它不处理STL结构,因为它是为C做的.SWIG会为你处理这个问题,但你可以自己将它包装在C中。这取决于你。
这是ctypes的Python文档:
http://docs.python.org/library/ctypes.html
此外,内置的dll应该与Python界面位于同一个文件夹中(为什么不是?)。
我很好奇,你为什么要从C ++中调用Python而不是直接调用C ++实现?