How to use C++ designed hardware drivers with Crappy ? — Crappy 1.5.3 documentation

# How to use C++ designed hardware drivers with Crappy ?

There is a lot of hardware only available for C++ or C platforms, but it can be easily bound with Python. Here we will give you an example on how to bind C language to make it object oriented (as used in Python).

## How to bind C/C++ with Crappy?

Here is detailed a complete example on how to bind C and C++ language with Python and add it to the Crappy package. Linux and Windows are both used for building.

Warning

This is not a C++ tutorial, a few programming notions are used here, please refer to the tutorials below if you are not a C or C++ developer. /n C++ tutorials:

> - openclassrooms (fr)

> - cplusplus (en)

C tutorials:

> - openclassroom (fr)

> - cprogramming (en)

Under Linux, you must install the python-dev package to ensure that you can use the Python.h library in the C or C++ code:

    sudo apt-get install python-dev

Under Windows, there is no python-dev package, but the python installer for Windows will install a subdirectory in the python dir directory:

> C:Pythoninclude which contains the Python.h.

## First example

### The C++ code (hello.cpp)

    // Python header contains all the functions definitions to handle python object in C/C++.
    #include <Python.h>
    // Header that defines the standard input/output stream objects.
    #include <iostream>
    // define the namespace to use.
    using namespace std;
    // The functions bound with python have to return a PyObject understandable in Python.
    static PyObject* hello(PyObject* self, PyObject* args){
        const char* name;
        // it parses the args argument and look for a string
        // and set the name var with the parsed value.
        // if it fails, PyArg_ParseTuple returns False, True otherwise.
        // returning NULL directly allows to raise an exception in Python.
        if(!PyArg_ParseTuple(args, "s", &name))
            return NULL;
        cout << "Hello " << name << endl;
        // This should be a void function, so we return the Python None.
        Py_RETURN_NONE;
    }
    // definition of a PyMethodDef object (defined in the python library)
    // contains the functions definition to be bound with python.
    static PyMethodDef HelloMethods[] =
    {
        {"hello", hello, METH_VARARGS, "Say hello to somebody."},
        {NULL, NULL, 0, NULL}
    };
    PyMODINIT_FUNC inithelloModule(void){
        (void) Py_InitModule("helloModule", HelloMethods);
    }

Each functions to bind have to return a PyObject pointer. Then, if a function needs to get arguments, they a passed to args, and first parsed in the function with PyArgs_ParseTuple. If the argument parsed is not a char it returns NULL, returning a NULL alows to directly raise a Python error. Here there is no need to return an object, so we return None (equivalent of C++ NULL).

Then, to bind the hello function we need to create a PyMethodDef which contains the function definition:

    {"hello", hello, METH_VARARGS, "Say hello to somebody."}

The first element will be the name of the function in Python. The second element is the function to bind. The third element is METH_VARARGS if the function gets arguments, or METH_NOARGS otherwise. The last element corresponds to a description of the function, to appear in the function help.

### Adding the binding to Crappy

To use the hello method defined in hello.cpp, we need to compile our project. This is automatically supported by the distutil package used to create the Crappy package.

We have to use the Extension module in distulil.core.

Example:

    helloModule = Extension('technical.helloModule',
                         sources=['sources/hello/hello.cpp'],
                         extra_compile_args=["-l", "python2.7"])
    extentions.append(helloModule)
    Extension takes several argument, the first one is the full name of the extension, including any
    packages. Not a filename or pathname, but Python dotted name. Here we want to put the extension in
    technical, to import our module as crappy.technical.helloModule, so the extension name is
    'technical.helloModule'.

Note

Here, we called the extension helloModule, so the init method must be defined as follows:

    PyMODINIT_FUNC inithelloModule(void){
        (void) Py_InitModule("helloModule", HelloMethods);
    }

the name of the function must be: init+[the name of your extension]: inithelloModule. Py_initModule must initialize a module with the same name “hellModule”.

Extensions is just a list containing all the extensions to build, so we must add the helloModule to it.

Finally, we import our module in technical/__init__.py.

Now we can build our module with:

    sudo python setup.py install

The module helloModule.so will end up in /usr/local/lib/python2.7/dist-packages/crappy2-X.X.X-py2.7-linux-x86_64.egg/crappy2/technical and a helloModule.py file will be created to allow the import of the module:

    def __bootstrap__():
        global __bootstrap__, __loader__, __file__
        import sys, pkg_resources, imp
        __file__ = pkg_resources.resource_filename(__name__, 'helloModule.so')
        __loader__ = None; del __bootstrap__, __loader__
        imp.load_dynamic(__name__,__file__)
    __bootstrap__()

So we can now simply use our module:

    In [1]: import crappy2
    In [2]: crappy2.technical.helloModule.hello("Crappy")
    Hello Crappy

## A more oriented object module

Let’s try to define a class that is similar to the following python class:

    class Hello:

        def __init__(self, name="Crappy"):
            self.name = name

        def say_hello(self):
            print 'hello ', self.name

        def get_name(self):
            return self.name

we first need to define the functions to construct our future class:

> - a new method

> - a constructor

> - a destructor And a structure which will contain the class attributes.

Here, the struct contains two elements. The first, PyObject_HEAD must always be defined, it represent the type of object. The second element represent our attribute ‘name’.:

    // defines a struct to build our Python module, this is similar to the dict of a Python class.
    typedef struct {
        PyObject_HEAD
        char *name;
    } Hello;

The new method parses the arguments and keywords arguments, to initialize the structure defined before, which will be passed as first argument for each method (similar to the python self).:

    // This function will be called at the creation of our Python class, it allocates memory, parses the
    arguments and returns
    // the self struct.
    static PyObject *Hello_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
    {
        Hello *self;
        self = (Hello *)type->tp_alloc(type, 0);
        static char *kwlist[] = {"name", NULL};
        if (self != NULL) {
            if (! PyArg_ParseTupleAndKeywords(args, kwds, "|s", kwlist, &self->name)){
                    return NULL;
            }
        }
        return (PyObject *)self;
    }

The constructor parses the arguments and keywords arguments. The “name” argument is optional: “|s” string or nothing; name is set by default to “Crappy”.:

    static int Hello_init(Hello *self, PyObject *args, PyObject *kwds)
    {
        static char *kwlist[] = {"name", NULL};
        self->name = "Crappy";
        if (! PyArg_ParseTupleAndKeywords(args, kwds, "|s", kwlist, &self->name)){
                return NULL;
        }
        return 0;
    }
    static void Hello_dealloc(Hello* self)
    {
        self->ob_type->tp_free((PyObject*)self);
    }

We then define our two methods like before:

Note

To return a value, we need to use the Py_BuildValue function, to convert C++ type to python type: this way, we directly get a understandable python object.:

    PyObject*
    Hello_get(Hello *self)
    {
        return Py_BuildValue("s", self->name);
    }
    PyObject*
    Hello_print(Hello *self)
    {
        cout << "Hello " << self->name << endl;
        Py_RETURN_NONE;
    }

To define a class which can be bound with Python, we need to define its structure, with a PyTypeObject. We have to define:

> - which function is the constructor

> - which one is the destructor, the new method…

> - what is the name of the class

> - its size

> - its methods

    static PyMethodDef Hello_methods[] = {
            {"say_hello", (PyCFunction)Hello_print, METH_NOARGS,
         "Say hello to somebody."},
         {"get_name", (PyCFunction)Hello_get, METH_NOARGS,
         "Return the name attribute."},
        {NULL}
    };
    static PyMethodDef module_methods[] = {
        {NULL}
    };
    static PyTypeObject helloType = {
        PyObject_HEAD_INIT(NULL)
        0,                         /*ob_size*/
        "Hello",             /*tp_name*/
        sizeof(Hello),             /*tp_basicsize*/
        0,                         /*tp_itemsize*/
        (destructor)Hello_dealloc, /*tp_dealloc*/
        0,                         /*tp_print*/
        0,                         /*tp_getattr*/
        0,                         /*tp_setattr*/
        0,                         /*tp_compare*/
        0,                         /*tp_repr*/
        0,                         /*tp_as_number*/
        0,                         /*tp_as_sequence*/
        0,                         /*tp_as_mapping*/
        0,                         /*tp_hash */
        0,                         /*tp_call*/
        0,                         /*tp_str*/
        0,                         /*tp_getattro*/
        0,                         /*tp_setattro*/
        0,                         /*tp_as_buffer*/
        Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
        "Hello objects",           /* tp_doc */
        0,                     /* tp_traverse */
        0,                     /* tp_clear */
        0,                     /* tp_richcompare */
        0,                     /* tp_weaklistoffset */
        0,                     /* tp_iter */
        0,                     /* tp_iternext */
        Hello_methods,             /* tp_methods */
        0,             /* tp_members */
        0,                         /* tp_getset */
        0,                         /* tp_base */
        0,                         /* tp_dict */
        0,                         /* tp_descr_get */
        0,                         /* tp_descr_set */
        0,                         /* tp_dictoffset */
        (initproc)Hello_init,      /* tp_init */
        0,                         /* tp_alloc */
        Hello_new,                 /* tp_new */
    };

Finally, as we did in the first example, the init method has to be defined:

    Py_InitModule3 creates the module and returns its instance (here empty).
    We can add our created objects, here helloType which defines our class.

Note

When returning an object, it returns a reference to it, each object has a reference counter that is automatically created for memory management issue, to know how many different places there are that have a reference to an object. When an object’s reference count becomes 0, the object is automatically deallocated. You have to take care of it when dealing with C-C++/Python bindings. (With Py_INCREF, Py_DECREF). Please see Python C-api documentation for more details.

    PyMODINIT_FUNC
    inithelloModule(void)
    {
        try{
            PyObject* m;
            if (PyType_Ready(&helloType) < 0)
                cout << "unable to install ximea module" << endl;
            m = Py_InitModule3("helloModule", module_methods,
                               "hello C++ module.");
            Py_INCREF(&helloType);
            PyModule_AddObject(m, "Hello", (PyObject *)&helloType);
        }
        catch ( const std::exception & e )
        {
            std::cerr << e.what();
        }
    }

Example:

    In [2]: hello = crappy2.technical.helloModule.Hello("world")

    In [3]: hello.
    hello.get_name   hello.say_hello

    In [3]: hello.get_name()
    Out[3]: 'world'

    In [4]: hello.say_hello()
    Hello world

    In [5]: hello = crappy2.technical.helloModule.Hello()

    In [6]: hello.say_hello()
    Hello Crappy

    In [7]: hello.get_name()
    Out[7]: 'Crappy'