c++ - 如何在 Python 中实现 C++ 类,以供 C++ 调用?

标签 c++ python swig boost-python

我有一个用 C++ 编写的类接口(interface)。我有几个实现此接口(interface)的类也是用 C++ 编写的。这些是在一个更大的 C++ 程序的上下文中调用的,该程序基本上实现了“main”。我希望能够用 Python 编写这个接口(interface)的实现,并允许它们在更大的 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++ 接口(interface)定义如下:

// 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
}

我希望这是足够清楚的;)

最佳答案

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


将现有接口(interface)暴露给 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

Exactly what you'd hope to see from that test.


Embedding the Python in the application:

Next up we need to implement a real version of your mymain.cc. I've put together a sketch of what it might look like:

#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

The final piece of the puzzle is being able to convert the PyObject* that you get from creating the instance in Python into a myif *. SWIG again makes this reasonably straightforward.

First we need to ask SWIG to expose its runtime in a headerfile for us. We do this with an extra call to SWIG:

swig -Wall -c++ -python -external-runtime runtime.h

Next we need to re-compile our SWIG module, explicitly giving the table of types SWIG knows about a name so we can look it up from within our main.cc. We recompile the .so using:

g++ -DSWIG_TYPE_TABLE=myif -Wall -Wextra -shared -o _module.so myif_wrap.cxx -I/usr/include/python2.7 -lpython2.7

Then we add a helper function for converting the PyObject* to myif* in our main.cc:

#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

关于c++ - 如何在 Python 中实现 C++ 类,以供 C++ 调用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25083939/

相关文章:

c++ - 带有模板类的 SWIG SHARED_PTR 宏

c++ - 如何清除 QTreeView/QFileSystemModel

c++ - Qt:windows函数是未解析的外部符号

c++ - 如何制作需要执行此操作的功能?

c# - 将参数中带有回调函数的 C/C++ 函数编码到 C#

java - Android套接字客户端不工作

python - python 中的条件日志记录

python - 如何用 pandas 中的有效日期替换超出范围的日期

python - Swig C++ python 包装器文件解释?

python - %exception 被 SWIG(python) 包装器忽略了吗?