How to expose a C++ class to Python without buildi

2019-02-22 17:32发布

问题:

I want to know if there is any way to expose a C++ class to Python but without building an intermediate shared library.

Here is my desirable scenario. For example I have following C++ class:

class toto
     {
     public:
        toto(int iValue1_, int iValue2_): iValue1(iValue1_), iValue2(iValue2_) {}
        int Addition(void) const {if (!this) return 0; return iValue1 + iValue2;}

      private:
        int iValue1;
        int iValue2;
     };

I would like to convert somehow this class (or its intance) to a PyObject* in order to send it as paremter (args) to for example PyObject_CallObject:

PyObject* PyObject_CallObject(PyObject* wrapperFunction, PyObject* args)

In the other hand in my python side, I'll have a wrapperFunction which gets the pointer on my C++ class (or its instance) as parameter and it calls its methods or uses its properties:

def wrapper_function(cPlusPlusClass):
    instance = cPlusPlusClass(4, 5)
    result = instance.Addition()

As you can see, I don't really need/want to have a separate shared library or build a module by boost python. All that I need is to find a way to convert a C++ code to PyObject and send it to python. I cannot find a way to do that by C python libraries, boost or SWIG.

Do you have any idea? Thanks for your help.

回答1:

As far as I know, there is no easy way to accomplish this.

To extend Python with C++ with neither a module nor an intermediate library, it would require dynamically loading a library, then importing the functions. This approach is used by the ctypes module. To accomplish the same with C++, one would need to write a ctypes-like library that understood the C++ ABI for the target compiler(s).

To extend Python without introducing a module, an intermediate library could be created that provided a C API that wraps the C++ library. This intermediate library could then be used in Python through ctypes. While it does not provide the exact calling syntax and does introduce an intermediate library, it would likely be less effort than building a ctypes-like library that could interface directly with C++.

However, if an intermediate library is going to be introduced, it may be worthwhile to use Boost.Python, SWIG, or some other C++/Python language binding tool. While many of these tools will introduce the extension via a module, they often provide cleaner calling conventions, better error checking in the binding process, and may be easier to maintain.



回答2:

I found my answer. Actually what I was searching was pretty similar to this answer (thanks moooeeeep for his comment):

  Exposing a C++ class instance to a python embedded interpreter

Following C++ class (Attention! default constructor is mandatory):

class TwoValues
{
public:
    TwoValues(void): iValue1(0), iValue2(0) {}
    TwoValues(int iValue1, int iValue2): iValue1(iValue1_), iValue2(iValue2_) {}

    int Addition(void) const {if (!this) return 0; return iValue1 + iValue2;}

public:
    int iValue1;
    int iValue2;
};

could be exposed by boost by following macro:

BOOST_PYTHON_MODULE(ModuleTestBoost)
{
class_<TwoValues>("TwoValues")
   .def("Addition",             &TWOVALUES::Addition)
   .add_property("Value1",      &TWOVALUES::iValue1)
   .add_property("Value2",      &TWOVALUES::iValue2);
};

In the other hand I have a python function defined in python_script.py which takes an instance of this class and do something. For example:

def wrapper_function(instance):
    result = instance.Addition()
    myfile = open(r"C:\...\testboostexample.txt", "w")
    output = 'First variable is {0}, second variable is {1} and finally the addition is {2}'.format(instance.Value1, instance.Value2, result)
    myfile .write(output)
    myfile .close()

Then in C++ side, I can call this function by sending at the same time the instance of my class, like this:

Py_Initialize();

try
    {
    TwoValues instance(5, 10);
    initModuleTestBoost();

    object python_script = import("python_script");
    object wrapper_function = python_script.attr("wrapper_function");
    wrapper_function(&instance);
    }
catch (error_already_set)
    {
    PyErr_Print();
    }

Py_Finalize();

Advantages:

  • I don't need build any shared library or binary
  • As I'm using Boost, I don't need to be worry about memory management & reference counting
  • I don't use shared boost pointer (boost::shared_ptr) to point to the instance of my class