How do I wrap a C++ class with Cython?

Endophage picture Endophage · Jan 19, 2012 · Viewed 15.6k times · Source

I have a C++ class. It's made up of one .ccp file and one .h file. It compiles (I can write a main method that uses it successfully in c++). How do I wrap this class with Cython to make it available in Python?

I've read the docs and don't follow. They talk about generating the cpp file. When I've tried to follow the docs, my already existing cpp gets blown away...

What am I meant to put in the pyx file? I've been told the class definition but how much of it? Just the public methods?

Do I need a .pxd file? I don't understand when this file is or isn't required.

I've tried asking these question in the #python IRC channel and can't get an answer.

Answer

Niklas R picture Niklas R · Jan 19, 2012

Even Cython is generally for use with C, it can generate C++ code, too. When compiling, you add the --cplus flag.

Now, creating a wrapper for the class is simple and not much different from wrapping a structure. It mainly differs from declaring the extern, but that's not much difference at all.

Suppose you have a class MyCppClass in mycppclass.h.

cdef extern from "mycppclass.h":
    cppclass MyCppClass:
        int some_var

        MyCppClass(int, char*)
        void doStuff(void*)
        char* getStuff(int)

cdef class MyClass:

    # the public-modifier will make the attribute public for cython,
    # not for python. Maybe you need to access the internal C++ object from
    # outside of the class. If not, you better declare it as private by just
    # leaving out the `private` modifier.
    # ---- EDIT ------
    # Sorry, this statement is wrong. The `private` modifier would make it available to Python,
    # so the following line would cause an error es the Pointer to MyCppClass
    # couldn't be converted to a Python object.
    #>> cdef public MyCppClass* cobj
    # correct is:
    cdef MyCppClass* obj

    def __init__(self, int some_var, char* some_string):
        self.cobj = new MyCppClass(some_var, some_string)
        if self.cobj == NULL:
            raise MemoryError('Not enough memory.')

    def __del__(self):
        del self.cobj

    property some_var:
        def __get__(self):
            return self.cobj.some_var
        def __set__(self, int var):
            self.cobj.some_var = var

Note that the new keyword is only available when the --cplus flag is set, otherwise use malloc from <stdlib.h> by externing it.

Also note that you don't need to dereference the pointer (->) to call the method. Cython tracks the object's type and applies what fits.

.pxd files are for seperating declarations from implementation, or to avoid namespace colliding. Imagine you'd like to name you Python-wrapper like the C++ class. Simply put in your .pxd file the extern declarations and cimport the pxd file in the .pyx.

cimport my_pxd
cdef my_pxd.SomeExternedType obj

Note that you can not write implementations in a .pxd file.