I l@ve RuBoard |
![]() ![]() |
19.8 Wrapping C++ Classes with SWIGOne of the neater tricks SWIG can perform is class wrapper generation -- given a C++ class declaration and special command-line settings, SWIG generates:
As before, simply run SWIG in your makefile to scan the C++ class declaration and compile its outputs. 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. 19.8.1 A Little C++ Class (But Not Too Much)To see how this all works, we need a C++ class. To illustrate, let's code a simple one to be used in Python scripts.[7] The following C++ files define a Number class with three methods (add, sub, display), a data member (data), and a constructor and destructor. Example 19-19 shows the header file.
Example 19-19. PP2E\Integrate\Extend\Swig\Shadow\number.hclass Number { public: Number(int start); ~Number( ); void add(int value); void sub(int value); void display( ); int data; }; And Example 19-20 is the C++ class's implementation file; each method prints a message when called to trace class operations. Example 19-20. PP2E\Integrate\Extend\Swig\Shadow\number.cxx#include "number.h" #include "iostream.h" // #include "stdio.h" Number::Number(int start) { data = start; cout << "Number: " << data << endl; // cout and printf both work // printf("Number: %d\n", data); // python print goes to stdout } Number::~Number( ) { cout << "~Number: " << data << endl; } void Number::add(int value) { data += value; cout << "add " << value << endl; } void Number::sub(int value) { data -= value; cout << "sub " << value << endl; } void Number::display( ) { cout << "Number = " << data << endl; } Just so that you can compare languages, here is how this class is used in a C++ program; Example 19-21 makes a Number object, call 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 19-21. PP2E\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( ); num->data = 99; // set C++ data member cout << num->data << endl; // fetch C++ data member num->display( ); delete num; } You can use the g++ command-line C++ compiler program to compile and run this code on Linux. If you don't run Linux, you'll have to extrapolate (there are far too many C++ compiler differences to list here). [mark@toy ~/.../PP2E/Integrate/Extend/Swig/Shadow]$ g++ main.cxx number.cxx [mark@toy ~/.../PP2E/Integrate/Extend/Swig/Shadow]$ a.out Number: 1 add 4 Number = 5 sub 2 Number = 3 99 Number = 99 ~Number: 99 19.8.2 Wrapping the C++ Class with SWIGLets get back to Python. To use the C++ Number class in Python scripts, you need to code or generate a glue logic layer between the two languages, as in prior examples. To generate that layer automatically, just write a SWIG input file like the one shown in Example 19-22. Example 19-22. PP2E\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. This time, SWIG uses the class declaration to generate three files, and two different Python modules:
The Linux makefile shown in Example 19-23 combines the generated C++ wrapper code module with the C++ class implementation file to create a numberc.so,the dynamically loaded extension module that must be in a directory on your Python module search path when imported from a Python script. Example 19-23. PP2E\Integrate\Extend\Swig\Shadow\makefile.number-swig########################################################################### # Use SWIG to integrate the number.h C++ class for use in Python programs. # Note: name "numberc.so" matters, because shadow class imports numberc. ########################################################################### # unless you've run make install SWIG = ../myswig PY = $(MYPY) all: numberc.so number.py # wrapper + real class numberc.so: number_wrap.o number.o g++ -shared number_wrap.o number.o -o numberc.so # generated class wrapper module number_wrap.o: number_wrap.c number.h g++ number_wrap.c -c -g -I$(PY)/Include -I$(PY) number_wrap.c: 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 cxxtest: g++ main.cxx number.cxx clean: rm -f *.pyc *.o *.so core a.out force: rm -f *.pyc *.o *.so core a.out number.py number_wrap.c number_wrap.doc As usual, run this makefile to generate and compile the necessary glue code into an extension module that can be imported by Python programs: [mark@toy ~/....../Integrate/Extend/Swig/Shadow]$ make -f makefile.number-swig Generating wrappers for Python g++ number_wrap.c -c -g -I/... g++ -c -g number.cxx g++ -shared number_wrap.o number.o -o numberc.so To help demystify SWIG's magic somewhat, here is a portion of the generated C++ number_wrap.c accessor functions module. You can find the full source file at http://examples.oreilly.com/python2 (or simply generate it yourself ). Notice that this file defines a simple C extension module of functions that generally expect a C++ object pointer to be passed in (i.e., a "this" pointer in C++ lingo). This is a slightly different structure than Example 19-17, which wrapped a C type with a Python class instead, but the net effect is similar: ..._wrap function implementations that run C++ operation syntax... #define new_Number(_swigarg0) (new Number(_swigarg0)) static PyObject *_wrap_new_Number(PyObject *self, PyObject *args) { ...body deleted... } #define Number_add(_swigobj,_swigarg0) (_swigobj->add(_swigarg0)) static PyObject *_wrap_Number_add(PyObject *self, PyObject *args) { ...body deleted... } #define Number_data_get(_swigobj) ((int ) _swigobj->data) static PyObject *_wrap_Number_data_get(PyObject *self, PyObject *args) { ...body deleted... } static PyMethodDef numbercMethods[] = { { "Number_data_get", _wrap_Number_data_get, 1 }, { "Number_data_set", _wrap_Number_data_set, 1 }, { "Number_display", _wrap_Number_display, 1 }, { "Number_sub", _wrap_Number_sub, 1 }, { "Number_add", _wrap_Number_add, 1 }, { "delete_Number", _wrap_delete_Number, 1 }, { "new_Number", _wrap_new_Number, 1 }, { NULL, NULL } }; SWIGEXPORT(void,initnumberc)( ) { PyObject *m, *d; SWIG_globals = SWIG_newvarlink( ); m = Py_InitModule("numberc", numbercMethods); d = PyModule_GetDict(m); On top of the accessor functions module, SWIG generates number.py, the following shadow class that Python scripts import as the actual interface to the class. This code is a bit more complicated than the wrapper class we saw in the prior section, because it manages object ownership and therefore handles new and existing objects differently. The important thing to notice is that it is a straight Python class that saves the C++ "this" pointer of the associated C++ object, and passes control to accessor functions in the generated C++ extension module: import numberc class NumberPtr : def __init__(self,this): self.this = this self.thisown = 0 def __del__(self): if self.thisown == 1 : numberc.delete_Number(self.this) def add(self,arg0): val = numberc.Number_add(self.this,arg0) return val def sub(self,arg0): val = numberc.Number_sub(self.this,arg0) return val def display(self): val = numberc.Number_display(self.this) return val def __setattr__(self,name,value): if name == "data" : numberc.Number_data_set(self.this,value) return self.__dict__[name] = value def __getattr__(self,name): if name == "data" : return numberc.Number_data_get(self.this) raise AttributeError,name def __repr__(self): return "<C Number instance>" class Number(NumberPtr): def __init__(self,arg0) : self.this = numberc.new_Number(arg0) self.thisown = 1 A subtle thing: the generated C++ module file is named number_wrap.c, but the Python module name it gives in its initialization function is numberc, which is the name also imported by the shadow class. The import works because the combination of the glue code module and the C++ library file is linked into a file numberc.so such that the imported module file and initialization function names match. When using shadow classes and dynamic binding, the compiled object file's name must generally be the module name given in the .i file with an appended "c". In general, given an input file named interface.i: %module interface ...declarations... SWIG generates glue code file interface_wrap.c, which you should somehow compile into an interfacec.so file to be dynamically loaded on import: swig -python -shadow interface.i g++ -c interface.c interface_wrap.c ...more... g++ -shared interface.o interface_wrap.o -o interfacec.so The module name interface is reserved for the generated shadow class module, interface.py. Keep in mind that this implementation structure is subject to change at the whims of SWIG's creator, but the interface it yields should remain the same -- a Python class that shadows the C++ class, attribute for attribute.[8]
19.8.3 Using the C++ Class in PythonOnce the glue code is generated and compiled, Python scripts can access the C++ class as though it were coded in Python. Example 19-24 repeats the main.cxx file's class tests; here, though, the C++ class is being utilized from the Python programming language. Example 19-24. PP2E\Integrate\Extend\Swig\Shadow\main.pyfrom 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( ) num.data = 99 # set C++ data member, generated __setattr__ print num.data # get C++ data member, generated __getattr__ num.display( ) 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: [mark@toy ~/....../Integrate/Extend/Swig/Shadow]$ python main.py Number: 1 add 4 Number = 5 sub 2 Number = 3 99 Number = 99 ~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 19-21. If you really want to use the generated accessor functions module, you can, as shown in Example 19-25. Example 19-25. PP2E\Integrate\Extend\Swig\Shadow\main_low.pyfrom numberc 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) Number_data_set(num, 99) print Number_data_get(num) Number_display(num) delete_Number(num) This script generates the same output as main.py, 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 19-26 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 19-26. PP2E\Integrate\Extend\Swig\Shadow\main_subclass.pyfrom 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( ) num.data = 99 print num.data num.display( ) num.mul(2) # mul( ) is implemented in Python num.display( ) 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.data: [mark@toy ~/....../Integrate/Extend/Swig/Shadow]$ python main_subclass.py Number: 1 in Python add... add 4 Number = 5 sub 2 Number = 3 99 Number = 99 in Python mul... Number = 198 ~Number: 198 In other words, SWIG makes it easy to use C++ class libraries as base classes in your Python scripts. As usual, you can import the C++ class interactively to experiment with it some more: [mark@toy ~/....../Integrate/Extend/Swig/Shadow]$ python >>> import numberc >>> numberc.__file__ # the C++ class plus generated glue module './numberc.so' >>> 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 (<C Number instance>, <C Number instance>) >>> 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 So what's the catch? Nothing much, really, but if you start using SWIG in earnest, the biggest downside is that SWIG cannot handle every feature of C++ today. If your classes use advanced C++ tools such as operator overloading and templates, you may need to hand-code simplified class type declarations for SWIG, instead of running SWIG over the original class header files. Also, SWIG's current string-based pointer representation sidesteps conversion and type-safety issues and works well in most cases, but it has sometimes been accused of creating performance or interface complications when wrapping existing libraries. 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 manual 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.
![]() |
I l@ve RuBoard |
![]() ![]() |