Search code examples
pythonc++boostboost-python

Run python in C++


I have an application written in C++ and a testing system (also in C++). Testing system is pretty complicated and hard to change (I want to make only small changes). My class looks like this:

class Derived : public Base {
public:
    void somefunc(const AnotherClass& file) {
}
};

There are several functions inside. My testing system creates Derived class instance and then uses it's methods to do some stuff.

Now I want to be able to write a solution in Python. I need a two-way integration. My idea is to write Python function, which will be executed every time when somefunc is called. And I don't want to lose variables' values in Python from one launch of function to another. And I also want to be able to use methods which are defined in the Base class instance from python. How can I achieve these things?

I chose Boost.Python for these purposes. For now, I understand, how to use c++ function, or even simple class in Python after some work. But I don't understand how to launch Python function from c++.

The second question - is Boost.Python a good choice? I need something very fast and, at the same time, easy to use.

Thank you for your help.


Solution

  • I would recommend using Cython for this sort of thing. Adapted examples from another question. (Edit: Upon request, I added an extended example that wraps a C++ class, see further below.)


    Edit: Simple example, one way (C++ -> Python).

    quacker.py:

    def quack():
        print("Quack!")
    

    cquacker.pyx:

    from quacker import quack
    
    cdef public void cquack():
        quack()
    

    main.cpp:

    #if _WIN32
    #include <direct.h>
    #define getcwd _getcwd
    #define PATH_SEPARATOR ';'
    #else
    #include <unistd.h>
    #define PATH_SEPARATOR ':'
    #endif
    
    #include <iostream>
    #include <string>
    #include <sstream>
    
    #include <Python.h>
    #include "cquacker.h"
    
    std::wstring getSysPath()
    {
      char cwd[FILENAME_MAX];
      getcwd(cwd, FILENAME_MAX);
      std::wstringstream path;
      path << Py_GetPath() << PATH_SEPARATOR << cwd;
      return path.str();
    }
    
    int main()
    {
      Py_Initialize();
      PySys_SetPath(getSysPath().c_str());
      PyInit_cquacker();
      if (PyErr_Occurred())
      {
        PyErr_Print();
        return -1;
      }
      cquack();
      Py_Finalize();
      return 0;
    }
    

    Edit: Extended example, round trip (C++ -> Python -> C++).

    quacker.py:

    def qcallback(duck):
        duck.quack()
    

    quacker/Duck.hpp

    #include <iostream>
    
    namespace quacker {
    
    class Duck
    {
    public:
        void quack() { std::cout << "Quack!" << "\n"; }
    };
    
    }
    

    cquacker_defs.pxd:

    cdef extern from "quacker/Duck.hpp" namespace "quacker":
        cdef cppclass Duck:
            Duck() except +
            void quack()
    

    cquacker.pyx:

    from cython.operator cimport dereference as deref
    from libcpp.memory cimport shared_ptr
    
    cimport cquacker_defs
    
    from quacker import qcallback
    
    cdef class Duck:
        cdef shared_ptr[cquacker_defs.Duck] _this
    
        @staticmethod
        cdef inline Duck _from_this(shared_ptr[cquacker_defs.Duck] _this):
            cdef Duck result = Duck.__new__(Duck)
            result._this = _this
            return result
    
        def __init__(self):
            self._this.reset(new cquacker_defs.Duck())
    
        def quack(self):
            assert self._this != NULL
            deref(self._this).quack()
    
    
    cdef public void cqcallback(shared_ptr[cquacker_defs.Duck] duck):
        qcallback(Duck._from_this(duck))
    

    main.cpp:

    #if _WIN32
    #include <direct.h>
    #define getcwd _getcwd
    #define PATH_SEPARATOR ';'
    #else
    #include <unistd.h>
    #define PATH_SEPARATOR ':'
    #endif
    
    #include <iostream>
    #include <memory>
    #include <string>
    #include <sstream>
    
    #include "quacker/Duck.hpp"
    
    #include <Python.h>
    #include "cquacker.h"
    
    std::wstring getSysPath()
    {
      char cwd[FILENAME_MAX];
      getcwd(cwd, FILENAME_MAX);
      std::wstringstream path;
      path << Py_GetPath() << PATH_SEPARATOR << cwd;
      return path.str();
    }
    
    int main()
    {
      Py_Initialize();
      PySys_SetPath(getSysPath().c_str());
      PyInit_cquacker();
      if (PyErr_Occurred())
      {
        PyErr_Print();
        return -1;
      }
      auto duck = std::make_shared<quacker::Duck>();
      cqcallback(duck);
      Py_Finalize();
      return 0;
    }