Section 22.10. Wrapping C Classes with SWIG


22.10. Wrapping C++ Classes with SWIG

One of the more clever tricks SWIG can perform is class wrapper generation. Given a C++ class declaration and special command-line settings, SWIG generates the following:

  • A C++-coded Python extension module with accessor functions that interface with the C++ class's methods and members

  • A Python-coded module with a wrapper class (called a "shadow" or "proxy" class in SWIG-speak) that interfaces with the C++ class accessor functions module

As before, to use SWIG in this domain, write and debug your class as though it would be used only from C++. Then, simply run SWIG in your makefile to scan the C++ class declaration and compile and link its output. The end result is that by importing the shadow class in your Python scripts, you can utilize C++ classes as though they were really coded in Python. Not only can Python programs make and use instances of the C++ class, they can also customize it by subclassing the generated shadow class.

22.10.1. A Simple C++ Extension Class

To see how this works, we need a C++ class. To illustrate, let's code a simple one to be used in Python scripts.[*] The following C++ files define a Number class with three methods (add, sub, display), a data member (data), and a constructor and destructor. Example 22-18 shows the header file.

[*] For a more direct comparison, you could translate the stack type in Example 22-15 to a C++ class too, but that yields much more C++ code than I care to show in this Python book.

Example 22-18. PP3E\Integrate\Extend\Swig\Shadow\number.h

 class Number { public:     Number(int start);             // constructor     ~Number( );                     // destructor     void add(int value);           // update data member     void sub(int value);     int  square( );                 // return a value     void display( );                // print data member     int data; }; 

Example 22-19 is the C++ class's implementation file; each method prints a message when called to trace class operations.

Example 22-19. PP3E\Integrate\Extend\Swig\Shadow\number.cxx

 /////////////////////////////////////////////////////////////// // implement a C++ class, to be used from Python code or not; // caveat: cout and print usually both work, but I ran into // an issue on Cygwin that prompted printf due to lack of time /////////////////////////////////////////////////////////////// #include "number.h" #include "stdio.h"                       // versus #include "iostream.h" Number::Number(int start) {     data = start;                        // python print goes to stdout     printf("Number: %d\n", data);        // cout << "Number: " << data << endl; } Number::~Number( ) {     printf("~Number: %d\n", data); } void Number::add(int value) {     data += value;     printf("add %d\n", value); } void Number::sub(int value) {     data -= value;     printf("sub %d\n", value); } int Number::square( ) {     printf("Square = ");     return data * data; } void Number::display( ) {     printf("Number = %d\n", data); } 

Just so that you can compare languages, the following is how this class is used in a C++ program. Example 22-20 makes a Number object, calls its methods, and fetches and sets its data attribute directly (C++ distinguishes between "members" and "methods," while they're usually both called "attributes" in Python).

Example 22-20. PP3E\Integrate\Extend\Swig\Shadow\main.cxx

 #include "iostream.h" #include "number.h" main( ) {     Number *num;     num = new Number(1);            // make a C++ class instance     num->add(4);                    // call its methods     num->display( );     num->sub(2);     num->display( );     cout << num->square( ) << endl;     num->data = 99;                 // set C++ data member     cout << num->data << endl;      // fetch C++ data member     num->display( );     cout << num << endl;            // print raw instance ptr     delete num;                     // run destructor } 

You can use the g++ command-line C++ compiler program to compile and run this code on Cygwin (it's the same on Linux). If you don't use a similar system, you'll have to extrapolate (there are far too many C++ compiler differences to list here). Type the compile command directly or use the cxxtest target in this directory's makefile, and then run the purely C++ program created:

 .../PP3E/Integrate/Extend/Swig/Shadow$ make -f makefile.number-swig cxxtest g++ main.cxx number.cxx -Wno-deprecated .../PP3E/Integrate/Extend/Swig/Shadow$ ./a.exe Number: 1 add 4 Number = 5 sub 2 Number = 3 Square = 9 99 Number = 99 0x4a0248 ~Number: 99 

22.10.2. Wrapping the C++ Class with SWIG

Let's get back to Python. To use the C++ Number class of the preceding section in Python scripts, you need to code or generate a glue logic layer between the two languages, just as in prior C extension examples. To generate that layer automatically, write a SWIG input file like the one shown in Example 22-21.

Example 22-21. PP3E\Integrate\Extend\Swig\Shadow\number.i

 /********************************************************  * Swig module description file for wrapping a C++ class.  * Generate by saying "swig -python -shadow number.i".  * The C module is generated in file number_wrap.c; here,  * module 'number' refers to the number.py shadow class.  ********************************************************/ %module number %{ #include "number.h" %} %include number.h 

This interface file simply directs SWIG to read the C++ class's type signature information from the included number.h header file. SWIG uses the class declaration to generate two different Python modules again:


number_wrap.cxx

A C++ extension module with class accessor functions.


number.py

A Python shadow class module that wraps accessor functions.

The former must be compiled into a binary library. The latter imports and uses the former and is the file that Python scripts ultimately import. As for simple functions, SWIG achieves the integration with a combination of Python and C++ code.

After running SWIG, the Cygwin makefile shown in Example 22-22 combines the generated number_wrap.cxx C++ wrapper code module with the C++ class implementation file to create a _number.dlla dynamically loaded extension module that must be in a directory on your Python module search path when imported from a Python script, along with the generated number.py.

As before, the compiled C extension module must be named with a leading underscore in SWIG today: _number.dll, rather than the numberc.dll format used by earlier releases. The shadow class module number.py internally imports _number.dll.

Example 22-22. PP3E\Integrate\Extend\Swig\Shadow\makefile.number-swig

 ########################################################################### # Use SWIG to integrate the number.h C++ class for use in Python programs. # Note: name "_number.dll" matters, because shadow class imports _number. ########################################################################### PYLIB = /usr/bin PYINC = /usr/include/python2.4 all: _number.dll number.py # wrapper + real class _number.dll: number_wrap.o number.o         g++ -shared number_wrap.o number.o -L$(PYLIB) -lpython2.4 -o $@ # generated class wrapper module(s) number_wrap.o: number_wrap.cxx number.h         g++ number_wrap.cxx -c -g -I$(PYINC) number_wrap.cxx: number.i         swig -c++ -python -shadow number.i number.py: number.i         swig -c++ -python -shadow number.i # wrapped C++ class code number.o: number.cxx number.h         g++ -c -g number.cxx -Wno-deprecated # non Python test cxxtest:         g++ main.cxx number.cxx -Wno-deprecated clean:         rm -f *.pyc *.o *.dll core a.exe force:         rm -f *.pyc *.o *.dll core a.exe \                 number_wrap.doc number_wrap.cxx number.py 

As usual, run this makefile to generate and compile the necessary glue code into an extension module that can be imported by Python programs:

 .../PP3E/Integrate/Extend/Swig/Shadow$ make -f makefile.number-swig swig -c++ -python -shadow number.i g++ number_wrap.cxx -c -g -I/usr/include/python2.4 g++ -c -g number.cxx -Wno-deprecated g++ -shared number_wrap.o number.o -L/usr/bin -lpython2.4 -o _number.dll 

22.10.3. Using the C++ Class in Python

Once the glue code is generated and compiled, Python scripts can access the C++ class as though it were coded in Python. In fact, it isthe shadow class on top of the extension module is generated Python code. Example 22-23 repeats the main.cxx file's class tests; here, though, the C++ class is being utilized from the Python programming language.

Example 22-23. PP3E\Integrate\Extend\Swig\Shadow\main.py

 from number import Number       # use C++ class in Python (shadow class)                                 # runs same tests as main.cxx C++ file num = Number(1)                 # make a C++ class object in Python num.add(4)                      # call its methods from Python num.display( )                       # num saves the C++ 'this' pointer num.sub(2) num.display( ) res = num.square( )              # converted C++ return value print res num.data = 99                   # set C++ data member, generated _ _setattr_ _ print num.data                  # get C++ data member, generated _ _getattr_ _ num.display( ) print num                       # runs repr in shadow/proxy class del num                         # runs C++ destructor automatically 

Because the C++ class and its wrappers are automatically loaded when imported by the number shadow class, you run this script like any other:

 .../PP3E/Integrate/Extend/Swig/Shadow$ python main.py Number: 1 add 4 Number = 5 sub 2 Number = 3 Square = 9 99 Number = 99 <number.Number; proxy of C++ Number instance at _b0974700_p_Number> ~Number: 99 

This output is mostly coming from the C++ class's methods and is the same as the main.cxx results shown in Example 22-20 (less the instance output format; it's a Python shadow class instance now).

22.10.3.1. Using the low-level extension module

If you really want to use the generated accessor functions module, you can, as shown in Example 22-24. This version runs the C++ extension module directly without the shadow class, to demonstrate how the shadow class maps calls back to C++.

Example 22-24. PP3E\Integrate\Extend\Swig\Shadow\main_low.py

 from _number import *           # same test as main.cxx                                 # use low-level C accessor function interface num = new_Number(1) Number_add(num, 4)              # pass C++ 'this' pointer explicitly Number_display(num)             # use accessor functions in the C module Number_sub(num, 2) Number_display(num) print Number_square(num) Number_data_set(num, 99) print Number_data_get(num) Number_display(num) print num delete_Number(num) 

This script generates essentially the same output as main.py, though the C++ class instance is something lower level than the proxy class here:

 .../PP3E/Integrate/Extend/Swig/Shadow$ python main_low.py Number: 1 add 4 Number = 5 sub 2 Number = 3 Square = 9 99 Number = 99 <Swig Object at _400d4900_p_Number> ~Number: 99 

22.10.3.2. Subclassing the C++ class in Python

Using the extension module directly works, but there is no obvious advantage to moving from the shadow class to functions here. By using the shadow class, you get both an object-based interface to C++ and a customizable Python object. For instance, the Python module shown in Example 22-25 extends the C++ class, adding an extra print statement to the C++ add method and defining a brand-new mul method. Because the shadow class is pure Python, this works naturally.

Example 22-25. PP3E\Integrate\Extend\Swig\Shadow\main_subclass.py

 from number import Number       # sublass C++ class in Python (shadow class) class MyNumber(Number):     def add(self, other):         print 'in Python add...'         Number.add(self, other)     def mul(self, other):         print 'in Python mul...'         self.data = self.data * other num = MyNumber(1)               # same test as main.cxx num.add(4)                      # using Python subclass of shadow class num.display()                   # add( ) is specialized in Python num.sub(2) num.display( ) print num.square( ) num.data = 99 print num.data num.display( ) num.mul(2)                      # mul( ) is implemented in Python num.display( ) print num                       # repr from shadow superclass del num 

Now we get extra messages out of add calls, and mul changes the C++ class's data member automatically when it assigns self.dataPython code extends C++ code:

 .../PP3E/Integrate/Extend/Swig/Shadow$ python main_subclass.py Number: 1 in Python add... add 4 Number = 5 sub 2 Number = 3 Square = 9 99 Number = 99 in Python mul... Number = 198 <_ _main_ _.MyNumber; proxy of C++ Number instance at _580d4900_p_Number> ~Number: 198 

In other words, SWIG makes it easy to use C++ class libraries as base classes in your Python scripts. Among other things, this allows us to leverage existing C++ class libraries in Python scripts and optimize by coding parts of class hierarchies in C++ when needed.

22.10.3.3. Exploring the wrappers interactively

As usual, you can import the C++ class interactively to experiment with it some more:

 .../PP3E/Integrate/Extend/Swig/Shadow$ python >>> import _number >>> _number._ _file_ _                # the C++ class plus generated glue module '_number.dll' >>> import number                  # the generated Python shadow class module >>> number._ _file_ _ 'number.pyc' >>> x = number.Number(2)          # make a C++ class instance in Python Number: 2 >>> y = number.Number(4)          # make another C++ object Number: 4 >>> x, y (<number.Number; proxy of C++ Number instance at _a0764900_p_Number>,  <number.Number; proxy of C++ Number instance at _508b4900_p_Number>) >>> x.display( )                   # call C++ method (like C++ x->display( )) Number = 2 >>> x.add(y.data)                   # fetch C++ data member, call C++ method add 4 >>> x.display( ) Number = 6 >>> y.data = x.data + y.data + 32           # set C++ data member >>> y.display( )                           # y records the C++ this pointer Number = 42 >>> y.square( )                            # method with return value Square = 1764 >>> t = y.square( ) Square = >>> >>> t, type(t) (1764, <type 'int'>) 

Naturally, this example uses a small C++ class to underscore the basics, but even at this level, the seamlessness of the Python-to-C++ integration we get from SWIG is astonishing. Python code uses C++ members and methods as though they are Python code. Moreover, this integration transparency still applies once we step up to more realistic C++ class libraries.

So what's the catch? Nothing much, really, but if you start using SWIG in earnest, the biggest downside may be that SWIG cannot handle every feature of C++ today. If your classes use some esoteric C++ tools (and there are many), you may need to handcode simplified class type declarations for SWIG instead of running SWIG over the original class header files. SWIG development is ongoing, so you should consult the SWIG manuals and web site for more details on these and other topics.

In return for any such trade-offs, though, SWIG can completely obviate the need to code glue layers to access C and C++ libraries from Python scripts. If you have ever coded such layers by hand in the past, you already know that this is a very big win.

If you do go the handcoded route, though, consult Python's standard extension manuals for more details on both API calls used in this and the next chapter, as well as additional extension tools we don't have space to cover in this text. C extensions can run the gamut from short SWIG input files to code that is staunchly wedded to the internals of the Python interpreter; as a rule of thumb, the former survives the ravages of time much better than the latter.




Programming Python
Programming Python
ISBN: 0596009259
EAN: 2147483647
Year: 2004
Pages: 270
Authors: Mark Lutz

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net