Friday 29 March 2013

Using Python within C++

I tried to think of a Jake the Snake reference... but failed.

I now have a working (and easily available) package for programming my Lego Mindstorms NXT (look here for more info). The downside is that it's written in python and I'm more of a C++ kind of guy. I could root around to try to find something that's C compatible but as this was easily available in the Mint repos, I thought it might be a better idea to just call the python code within C++.

This is not as easy as you'd think, or at least, it isn't if you want to do it right. If you only care about running some commands through the Python interpreter and not paying any attention to the results, the you can easily use something like the following:

Py_Initialize();
PyRun_SimpleString("import os");
PyRun_SimpleString("print 'hello world'");
Py_Finalize();

This is essentially a python version of a system() call (kindof..).

Anyway, we're getting ahead of ourselves. First, you need to gain access to the python headers and libraries. Through Linux, this is fairly trivial as I would guess all distros would have the python2.7-dev (or whatever version you choose) available - note that it may not be installed by default though. For Qt, you can then add the following to your .pro file:

 LIBS += -Lpython2.7 -lpython2.7

and then you can include the python header:

 #include <python2.7/Python.h>

Putting the simple hello world code into a main function should build and run as expected. So that's the basics of accessing python within C++. How do you go about integrating this properly into your code though? For that, you need to go a bit more in depth into how python is written and how it handles memory and objects.

One of the main selling points of python is it's garbage collection and object reference handling - it deals with all references to any objects and deletes objects that don't have any. Within the framework of the python language, this is fine. However, when you're poking around under the hood, you have to be careful to do what python usually does for you. In other words, you have to be very careful about tracking object references that get passed back to you from function calls (and nearly all function calls pass back object references!).

To integrate with the PythonNXT python module, I've found the following functions the most useful. There are many others (obviously) that can be found here but this should give you the basic idea of what's going on and what you need to be careful about. The main things to remember is that everything returns/deals with a PyObject base type (or actually, a pointer to one) and you have to keep track of anything that gets returned to you from the API functions.

Py_Initialize

Initialises the python interpreter. Call this before doing anything else!

Py_Finalize

Shuts everything down to do with the interpreter. Don't call anything after this!

PyObject* PyImport_ImportModule(const char *name)

This will import the given module and return a new reference to the module object.

PyObject* PyObject_GetAttrString(PyObject *o, const char *attr_name)

This will return the named object associated with the given  object. For example, if you've just loaded a module and want to call a function, use this with the function name to return a new reference to that function. Or, you have a python object that you want to call a function on, use this to get a reference to that function and then use PyObject_CallObject to actually call the function.

PyObject* PyObject_CallObject(PyObject *callable_object, PyObject *args)

When passed a reference to a function (say from the above, PyObject_GetAttrString), this will call the given function with arguments given (specified as Python objects). This returns a new reference which will be the pythonified version of the actual function return value.

PyObject* Py_BuildValue(const char *format, ...)

This constructs a python object (tuple, list, single value, etc.) based on the given format and supplied arguments (e.g. Py_BuildValue("(i)", 1) will give a tuple of one integer value). Used for PyObject_CallObject above - note that it seems you should always create tuples for this, not just single values (e.g. "(i)" rather than "i")!

void Py_XDECREF(PyObject *o)

This will decrease the reference count of the given object and therefore delete it if the count goes to 0. Note that this version checks for NULL pointers being passed. This is the main thing to remember - you must call this on any returned objects when you're done with them unless the function returns a 'borrowed' (rather than 'new') reference. If you don't, you'll be leaking memory like sieve.


This covers the basics of using Python code through the C-API. In addition to these, the following (and related) are worth looking up to manipulate Lists and Tuples. They're fairly self-explanatory:

PyList_GetItem
PyTuple_GetItem


Finally, the basic types (int, double, etc.) have dedicated python objects associated so you can easily cast to these objects (e.g. PyIntObject) to access the actual data values from these objects.

In a future post, I'll go into a bit of detail how I've used this to create a basic interface to the PythonNXT module through C++/Qt.

No comments:

Post a Comment