VIVAHATE

October 2009

Extending Python with ctypes

ctypes is a foreign function library for Python. It provides C compatible data types, and allows calling functions in DLLs or shared libraries. It can be used to wrap these libraries in pure Python.

This tutorial assumes a Debian based GNU/Linux distribution. You may have to install build-essential.

Writing your shared library

We’ll start by creating our shared library. Type the following into your favorite text editor and save the file as testlib.c:

const char *say_hello() {
    return "Hello, World\n";
}

Compile and link thusly:

gcc -g -c -Wall testlib.c
gcc -shared -Wl,-soname,libtestlib.so.1 -o libtestlib.so.1 testlib.o -lc

If you didn’t encounter any errors, you should be left with a file named libtestlib.so.1 in the same directory as your source.

Next, you’ll want to make sure that ldconfig can load your library. The easiest way to do this is to create a file named testlib.conf in /etc/ld.so.conf.d/ containing the path where your shared library can be found.

Run the following command to verify that ldconfig can see your library:

ldconfig -v | grep testlib

With luck you should see something like this:

/home/kyle/src/ctypes_test/testlib:
	libtestlib.so.1 -> libtestlib.so.1

Using your shared library with ctypes

Fire up the interactive interpreter and type the following:

>>> from ctypes import cdll, c_char_p
>>> testlib = cdll.LoadLibrary('libtestlib.so.1')
>>> testlib.say_hello.restype = c_char_p
>>> testlib.say_hello()
Hello, World
>>>

You should, of course, see Hello, World displayed as the return value.

A little abstraction never hurts

Here’s a quick example of making your ctypes bindings a little easier to use:

from ctypes import cdll, c_char_p

class TestLib:
    def __init__(self):
        self.lib = cdll.LoadLibrary("libtestlib.so.1")
        self.lib.say_hello.restype = c_char_p

    def say_hello(self):
        return self.lib.say_hello()

testlib = TestLib()
print testlib.say_hello()

OBJ Loader

Loads face and vertex data from .OBJ files. Doesn’t support groups, materials, texture coords, normals, etc… Not a particularly robust solution, but it’s simple and terse. This is a work in progress.

import re

def load_obj(filename):
    verts = []
    faces = []
    f = open(filename, 'r')
    lexemizer = re.compile(r'([^\s]+)')
    for l in f.readlines():
        if l[0] == '#': continue
        tokens = lexemizer.findall(l)
        if tokens:
            if tokens[0] == 'v':
                v = [float(tokens[1]), float(tokens[2]), float(tokens[3])]
                verts.append(v)
            if tokens[0] == 'g':
                pass
            if tokens[0] == 'f':
                f = []
                for v in tokens[1:]:
                    f.append(int(v))
                faces.append(f)