Search code examples
pythonc++python-3.xword-wrap

How to wrap a C++ object using pure Python Extension API (python3)?


I want to know how to wrap a C++ object with Python Extension API (and distutils) without external tools (like Cython, Boost, SWIG, ...). Just in pure Python way without creating a dll.

Note that my C++ object has memory allocations so destructor has to be called to avoid memory leaks.

#include "Voice.h"

namespace transformation
{ 
  Voice::Voice(int fftSize) { mem=new double[fftSize]; } 

  Voice::~Voice() { delete [] mem; } 

  int Voice::method1() { /*do stuff*/ return (1); } 
}

I just want to do somethings like that in Python :

import voice

v=voice.Voice(512)
result=v.method1()

Solution

  • Seems that the answer was in fact here : https://docs.python.org/3.6/extending/newtypes.html

    With examples, but not really easy.

    EDIT 1 :

    In fact, it is not really for wrapping a C++ object in a Python object, but rather to create a Python object with C code. (edit2 : and so you can wrap C++ object!)

    EDIT 2 :

    Here is a solution using the Python newtypes

    .

    Original C++ file : Voice.cpp

    #include <cstdio>
    
    #include "Voice.h"
    
    namespace transformation
    { 
        Voice::Voice(int fftSize) {
            printf("c++ constructor of voice\n");
            this->fftSize=fftSize;
            mem=new double[fftSize];
            } 
    
        Voice::~Voice() { delete [] mem; } 
    
        int Voice::filter(int freq) {
            printf("c++ voice filter method\n");
            return (doubleIt(3));
        }
        int Voice::doubleIt(int i) { return 2*i; }
    }
    

    .

    Original h file : Voice.h

    namespace transformation {
    
        class Voice {
        public:
            double *mem;
            int fftSize;
    
            Voice(int fftSize);
            ~Voice();
    
            int filter(int freq);
            int doubleIt(int i);
        };
    
    }
    

    .

    C++ Python wrapper file : voiceWrapper.cpp

    #include <Python.h>
    
    #include <cstdio>
    //~ #include "structmember.h"
    
    #include "Voice.h"
    
    using transformation::Voice;
    
    typedef struct {
        PyObject_HEAD
        Voice * ptrObj;
    } PyVoice;
    
    
    
    
    static PyModuleDef voicemodule = {
        PyModuleDef_HEAD_INIT,
        "voice",
        "Example module that wrapped a C++ object",
        -1,
        NULL, NULL, NULL, NULL, NULL
    };
    
    static int PyVoice_init(PyVoice *self, PyObject *args, PyObject *kwds)
    // initialize PyVoice Object
    {
        int fftSize;
    
        if (! PyArg_ParseTuple(args, "i", &fftSize))
            return -1;
    
        self->ptrObj=new Voice(fftSize);
    
        return 0;
    }
    
    static void PyVoice_dealloc(PyVoice * self)
    // destruct the object
    {
        delete self->ptrObj;
        Py_TYPE(self)->tp_free(self);
    }
    
    
    static PyObject * PyVoice_filter(PyVoice* self, PyObject* args)
    {
        int freq;
        int retval;
    
        if (! PyArg_ParseTuple(args, "i", &freq))
            return Py_False;
    
        retval = (self->ptrObj)->filter(freq);
    
        return Py_BuildValue("i",retval);
    }
    
    
    static PyMethodDef PyVoice_methods[] = {
        { "filter", (PyCFunction)PyVoice_filter,    METH_VARARGS,       "filter the mem voice" },
        {NULL}  /* Sentinel */
    };
    
    static PyTypeObject PyVoiceType = { PyVarObject_HEAD_INIT(NULL, 0)
                                        "voice.Voice"   /* tp_name */
                                    };
    
    
    PyMODINIT_FUNC PyInit_voice(void)
    // create the module
    {
        PyObject* m;
    
        PyVoiceType.tp_new = PyType_GenericNew;
        PyVoiceType.tp_basicsize=sizeof(PyVoice);
        PyVoiceType.tp_dealloc=(destructor) PyVoice_dealloc;
        PyVoiceType.tp_flags=Py_TPFLAGS_DEFAULT;
        PyVoiceType.tp_doc="Voice objects";
        PyVoiceType.tp_methods=PyVoice_methods;
        //~ PyVoiceType.tp_members=Noddy_members;
        PyVoiceType.tp_init=(initproc)PyVoice_init;
    
        if (PyType_Ready(&PyVoiceType) < 0)
            return NULL;
    
        m = PyModule_Create(&voicemodule);
        if (m == NULL)
            return NULL;
    
        Py_INCREF(&PyVoiceType);
        PyModule_AddObject(m, "Voice", (PyObject *)&PyVoiceType); // Add Voice object to the module
        return m;
    }
    

    .

    distutils file : setup.py

    from distutils.core import setup, Extension
    
    setup(name='voicePkg', version='1.0',  \
          ext_modules=[Extension('voice', ['voiceWrapper.cpp','Voice.cpp'])])
    

    .

    python test file : test.py

    import voice
    
    v=voice.Voice(512)
    result=v.filter(5)
    print('result='+str(result))
    

    .

    and magic :

    sudo python3 setup.py install
    python3 test.py
    

    Output is :

    c++ constructor of voice
    c++ voice filter method
    result=6
    

    Enjoy !

    Doom