Scripting languages like Perl, Python and Tcl are receiving a lot of attention nowadays - mainly because these languages facilitate Rapid Application Development and Prototyping. It has been shown time and again that using a language like Python cuts down development time drastically - with the added advantage that you get highly robust and flexible code. But there are situations in which a pure scripting approach does not work, typical examples being scientific applications which require high speed math and graphics routines or applications which need to control and coordinate hardware devices on a real time basis. What we need is a mixed-language paradigm in which traditional systems languages like C/C++ do the 'dirty' low-level work while the Scripting language acts as the overall supervisor. This article focuses on using an excellent program called the Simplified Wrapper and Interface Generator (SWIG) to integrate code written in C/C++ with the popular scripting language Python. The code fragments in this article have been tested on a Red Hat Linux (5.2) machine running Python ver 1.5.1.
SWIG is being developed by Dave Beazley and can be downloaded from www.swig.org. Installation is straightforward, just run ./configure and then make. Currently, SWIG supports Perl, Python, Tcl and FSF Guile.
Let us say we have a C function called add(a, b) which returns the sum of two numbers passed to it as arguments. We will see how the add function can be made Python-callable. We will create a file called arith.c which contains the following code:
int add(int a, int b) { return a+b; }Let us now execute the following command:
swig -python -module arith arith.cWe see that SWIG has created two new files, arith_wrap.c and arith_wrap.doc. We should now compile both arith.c and arith_wrap.c to produce object files, the command being:
gcc -I/usr/include/python1.5 -c arith.c arith_wrap.cThe object files arith.o and arith_wrap.o should now be combined to produced a shared object called arith.so:
ld -shared -o arith.so arith.o arith_wrap.oIf everything goes well, we will have a file called arith.so in our current directory. Here is a sample interaction using the arith module:
import arith >>>arith.add(10, 20) 30 >>>arith.add(10, -10) 0 >>>
We will find out! Let us add one more function to our arith.c file and then rebuild arith.so:
int fact (int n) { int f=1; while (n > 1){ f = f * n; n = n - 1; } return f; }Let us make a similar function in Python (store it in a file fact.py):
def fact(n): f = 1 while n > 1: f = f * n n = n - 1 return fWe will now write a crude profiling program, profile.py:
#!/usr/bin/python import fact, arith, time pyfact = fact.fact cfact = arith.fact # Measuring speed of cfact start = time.time() for i in range(1,100000): cfact(10) end = time.time() print 'C factorial function used', end-start, 'seconds' start = time.time() for i in range(1,100000): pyfact(10) end = time.time() print 'Python factorial function used', end-start, 'seconds'Here is the output on our old Pentium box:
C factorial function used 1.29531896114 seconds Python factorial function used 8.22897398472 seconds
SWIG generates wrappers not by looking at how your C code works internally, but by seeing the interface specification. Here is how we should have proceeded. First, create an interface file, arith.i. The file should contain the following lines:
%module arith extern int add(int a, int b); extern int fact(int n);Now generate the wrappers by running swig -python arith.i, compile into object files and use ld to create arith.so.
The PC's parallel port can be used to perform some very amusing hardware interfacing experiments. On Linux, we have functions like inb(), outb() etc which can be used to access I/O ports. Here is a C program which writes to the printer port:
#include <asm/io.h> int main() { iopl(3); outb(1, 0x378); }The program should be compiled as cc -O2 io.c and it should be executed with superuser privilege. The iopl call is required to make the IO ports accessible to user programs. How do we write a Python version of this program? Simple. Use SWIG to make Python callable versions of outb(), inb() and iopl(). Here is an io.c module tailored for SWIG:
#include <asm/io.h> int py_iopl(int level) { return iopl(level); } void py_outb(unsigned char val, int port) { outb(val, port); } unsigned char py_inb(int port) { return inb(port); }Run SWIG and generate the io.so file. Here is a sample interaction (remember, you should run the Python interpreter as root):
>>>import io >>>io.py_iopl(3) >>>io.py_outb(10, 0x378) >>>io.py_inb(0x378) 10 >>>
Global variables declared in your C module can be accessed from Python. We create a module example with two variables foo and baz:
int foo = 10; int baz = 20;The variables foo and baz are accessible in Python through an object called cvar:
>>>import example >>>example.cvar Global variables { foo, baz } >>>example.cvar.foo 10 >>>example.cvar.baz 20 >>>example.cvar.foo = 100 >>>example.cvar.foo 100 >>>
Accessing C++ classes is a bit tricky. Let us first create a header file with a simple class declaration. We call this zoo.h:
class Zoo{ int n; char animals[10][50]; public: Zoo(); void shut_up(char *animal); void display(); };Now we create an interface file zoo.i:
%module zoo %{ #include "zoo.h" %} class Zoo{ char animals[10][50]; int n; public: Zoo(); void shut_up(char *animal); void display(); };We generate the Python wrappers by running the command:
swig -python -c++ zoo.iHere comes our source file zoo.cc:
#include "zoo.h" #include <stdio.h> Zoo::Zoo() { n = 0; } void Zoo::shut_up(char *animal) { if (n < 10) { strcpy(animals[n], animal); n++; } } void Zoo::display() { int i; for(i = 0; i < n; i++) printf("%s\n", animals[i]); }We create the object files by running:
g++ -I/usr/include/python1.5 -c zoo.cc zoo_wrap.cAnd finally, we create the module zoo.so:
ld -shared -o zoo.so zoo.o zoo_wrap.o /usr/lib/libg++.so.2.7.2 /usr/lib/libstdc++.soHere is an interactive Python session with our zoo module:
Script started on Mon Dec 13 14:31:26 1999 [pce@bhim] ~/src/writings/swig/src/shadow$ python Python 1.5.1 (#1, Sep 3 1998, 22:51:17) [GCC 2.7.2.3] on linux-i386 Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam >>> import zoo >>> dir(zoo) ['Zoo_display', 'Zoo_shut_up', '__doc__', '__file__', '__name__', 'new_Zoo'] >>> z=zoo.new_Zoo() >>> zoo.Zoo_shut_up(z,'Tiger') >>> zoo.Zoo_shut_up(z,'Lion') >>> zoo.Zoo_display(z) Tiger Lion >>> z2=zoo.new_Zoo() >>> zoo.Zoo_shut_up(z2,'Python') >>> zoo.Zoo_display(z2) Python >>>The constructor in our class Zoo has been mapped to a function called new_Zoo. Similarly, the member functions shut_up and display have been mapped to Zoo_shut_up and Zoo_display.
It is possible to create Python classes which act as 'wrappers' around C++ classes. Here is a Python wrapper class for the C++ Zoo class:
from zoo import * class Zoo: def __init__(self): self.this = new_Zoo() def shut_up(self, animal): Zoo_shut_up(self.this, animal) def display(self): Zoo_display(self.this)Now, we can very easily write:
>>> z = Zoo() >>> z.shut_up('Tiger') >>> z.shut_up('Lion') >>> z.display() Tiger Lion >>>It is even possible to request SWIG to generate Python shadow classes automatically!
SWIG is a useful tool, easy to learn and easy to apply. Though we have only examined SWIG in the context of Python scripting, usage with other languages like Perl and Tcl is very similar. The SWIG Home Page is the definitive source for more information.