如何在Python中实现C ++类,由C ++调用?

时间:2012-01-27 22:03:03

标签: c++ python swig boost-python

我有一个用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
}

我希望这很清楚;)

6 个答案:

答案 0 :(得分:39)

这个答案分为两个部分。首先,您需要以允许Python实现随意覆盖部分内容的方式在Python中公开您的接口。然后你需要展示你的C ++程序(在main中如何调用Python。


将现有接口公开给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

您希望从该测试中看到什么。


在应用程序中嵌入Python:

接下来我们需要实现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中查找它。我们使用:

重新编译.so
g++ -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不是纯虚拟的,因此很复杂。我们去了:

  1. 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)
      ;
    }
    
  2. 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()
    
  3. 生成文件

    default:
           g++ -shared -fPIC -o baz.so baz.cpp -lboost_python `pkg-config python --cflags`
    
  4. 结果是:

    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之间架起桥梁的最通用和最强大的工具。

http://www.boost.org/doc/libs/1_48_0/libs/python/doc/

答案 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 ++实现?