Wednesday, August 3, 2011

Exploring ctypes (1)

A perennial question in Python is what to do when Python isn't fast enough, and a related one is how to work with C libraries from Python. As discussed in this SO post, there are several possibilities, here I want to start working with ctypes.

This is now a Python built-in module. I was a bit put off when I looked at the official docs, since all the early examples are windows and there is almost no mention of OS X. (However, I will have to make sure I understand what's there later), I'd start instead with Andrew Dalke's quick tutorial (here). So the first part of this is my code duplicating a few of his examples.

Getting started is literally one line, preceded by the import, and followed by the function call itself. That's wonderful!

>>> import ctypes
>>> libc = ctypes.CDLL("libc.dylib", ctypes.RTLD_GLOBAL)
>>> print libc.strlen("Hello world!")

It's pretty self-explanatory, if you're at all familiar with C, except for the constant RTLD_GLOBAL. Under the hood, the library libc uses the functions dlopen and friends defined in clfcn.h. (We touched on these once before, in the last example of this post).

The prototype for the C call to load a library is:

void *dlopen(const char *file, int mode);

RTLD_GLOBAL is one of several enumerated modes, and the value we provide is passed in as an argument to dlopen. You can read about dlopen here.

Here is printf:

>>> libc.printf("the number %d and string %s\n", 10, 'spam')
the number 10 and string spam

I'm not sure why this works, because in the standard C library, printf is in libstdio (ref)

The function printf returns the number of characters printed, or a negative value to indicate an error. The next example introduces the idea that frequently we need to tell ctypes the type of input arguments or return values.

>>> libc.atoi('3')
>>> libc.atof('3.14')
>>> libc.atof.restype = ctypes.c_double
>>> libc.atof('3.14')

The assumed return type is int, and in order to get this example to work we have to specify that the returned value is a double. Here's the prototype:

double atof (const char *str);

Argument types are specified like this:

>>> libc.atan2.argtypes = [ctypes.c_double, ctypes.c_double]
>>> libc.atan2.restype = ctypes.c_double
>>> libc.atan2(3.0, 4.0)
>>> import math
>>> math.atan2(3.0, 4.0)

Finally, here's another function, this one from the math library:

>>> libmath = ctypes.CDLL("libm.dylib", ctypes.RTLD_GLOBAL)
>>> libmath.sqrt.restype = ctypes.c_double
>>> libmath.sqrt(2)
>>> libmath.sqrt(2.0)
Traceback (most recent call last):
File "", line 1, in
ctypes.ArgumentError: argument 1: : Don't know how to convert parameter 1
>>> libmath.sqrt.argtypes = [ctypes.c_double]
>>> libmath.sqrt(2)

And here is a glowing testimonial about controlling a camera via USB with code written in Python.

It'd be interesting to see what we could do on that in OS X.

Still to do:

• finish Andrew's tutorial (lots more there)
• read the official docs
• try to apply it to the problem of finding binding sites (here)