Как я могу реализовать класс C ++ в Python, который будет вызываться C ++? - PullRequest
37 голосов
/ 28 января 2012

У меня есть интерфейс класса, написанный на 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 ]

39 голосов
/ 28 января 2012

Этот ответ состоит из двух частей.Во-первых, вам нужно представить свой интерфейс в 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, а не в int main() в C ++. Впрочем, добавить это позже довольно просто.

Сначала получим межфилиальный полиморфизм, работающий :

%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;
}

Это просто стандартный встраивание Python в другое приложение .Он работает и дает именно то, что вы хотели бы увидеть также:

g++ -Wall -Wextra -I/usr/include/python2.7 main.cc -o main -lpython2.7
./main
200.0
10
10

Последний кусок головоломки - это возможность преобразовать PyObject*, который вы получите от создания экземпляра в Pythonв 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

Затем мы добавляем вспомогательную функцию для преобразования PyObject* в myif* в нашем 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;
}

Наконец мы должны скомпилировать main.cc с -DSWIG_TYPE_TABLE=myif, и это даст:

./main
11
12 голосов
/ 31 января 2012

минимальный пример; обратите внимание, что это осложняется тем, что 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. Makefile

    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. Вы можете получить класс из Base в c ++, и он будет работать точно так же.

РЕДАКТИРОВАТЬ (извлечение объекта 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();

10 голосов
/ 28 января 2012

Цитирование 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

8 голосов
/ 01 февраля 2012

Основано на (очень полезном) ответе Евдокса Я взял его код и расширил его так, что теперь есть встроенный интерпретатор со встроенным модулем.

Этот ответ является эквивалентом Boost.Python моего ответа на основе SWIG .

Файл заголовка 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, большая часть которого основана на ответе Евдокса:

#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();
  }
}

Ключевая часть, которую я добавил здесь, выше и далее "как мне встроить Python, используя Boost.Python?" и "как мне расширить Python, используя Boost.python?" (на который ответил Евдокс) ответ на вопрос «Как сделать оба одновременно в одной программе?». Решение этой проблемы заключается в вызове PyImport_AppendInittab, который принимает функцию инициализации, которая обычно вызывается при загрузке модуля, и регистрирует ее как встроенный модуль. Таким образом, когда mycl.py сообщает import myif, он заканчивает тем, что импортирует встроенный модуль Boost.Python.

1 голос
/ 28 января 2012

Взгляните на Boost Python, это самый универсальный и мощный инструмент для соединения C ++ и Python.

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

0 голосов
/ 30 января 2012

Нет реального способа напрямую связать код C ++ с Python.

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 ++, внешний "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. Это ваше дело.

Вот документация по Python для ctypes:

http://docs.python.org/library/ctypes.html

Кроме того, встроенная библиотека должна находиться в той же папке, что и ваш интерфейс Python (почему бы и нет?).

Мне любопытно, почему вы хотите вызывать Python изнутри C ++ вместо прямого вызова реализации C ++?

...