Wrapping C++ Classes with SWIG

One 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.

.8.1 A Little C++ Class (But Not Too Much)

To see how this all works, we need a C++ class. To illustrate, lets 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.

[7] For a more direct comparison, you could translate the stack type in Example 19-15 to a C++ class too, but that yields much more C++ code than I care to show in this Python book. Moreover, such a translation would sacrifice the types operator overloading features (SWIG does not currently map C++ operator overloads).

Example 19-19. PP2EIntegrateExtendSwigShadow umber.h

class 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++ classs implementation file; each method prints a message when called to trace class operations.

Example 19-20. PP2EIntegrateExtendSwigShadow umber.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 ", 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 e usually both called "attributes" in Python).

Example 19-21. PP2EIntegrateExtendSwigShadowmain.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 run Linux, youll 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

.8.2 Wrapping the C++ Class with SWIG

Lets 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. PP2EIntegrateExtendSwigShadow umber.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 umber 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++ classs 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. PP2EIntegrateExtendSwigShadowmakefile.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 youve 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 SWIGs 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 "" 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 files 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 SWIGs creator, but the interface it yields should remain the same -- a Python class that shadows the C++ class, attribute for attribute.[8]

[8] While I wrote this, Guido suggested a few times that a future Python release may merge the ideas of Python classes and C types more closely, and may even be rewritten in C++ to ease C++ integration in general. If and when that happens, its possible that SWIG may use C types to wrap C++ classes, instead of the current accessor functions + Python class approach. Or not. Watch http://www.swig.org for more recent developments beyond the details presented in this book.

.8.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. Example 19-24 repeats the main.cxx files class tests; here, though, the C++ class is being utilized from the Python programming language.

Example 19-24. PP2EIntegrateExtendSwigShadowmain.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++ his 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++ classs 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. PP2EIntegrateExtendSwigShadowmain_low.py

from numberc import * # same test as main.cxx # use low-level C accessor function interface num = new_Number(1) Number_add(num, 4) # pass C++ his 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. PP2EIntegrateExtendSwigShadowmain_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( ) 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++ classs 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__ umber.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 (, ) >>> 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 whats 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, SWIGs 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 Pythons 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 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.

Mixing Python and C++

Pythons standard implementation is currently coded in C, so all the normal rules about mixing C programs with C++ programs apply to the Python interpreter. In fact, there is nothing special about Python in this context, but here are a few pointers.

When embedding Python in a C++ program, there are no special rules to follow. Simply link in the Python library and call its functions from C++. Pythons header files automatically wrap themselves in extern "C" {...} declarations to suppress C++ name-mangling. Hence, the Python library looks like any other C component to C++; there is no need to recompile Python itself with a C++ compiler.

When extending Python with C++ components, Python header files are still C++-friendly, so Python API calls in C++ extensions work like any other C++ to C call. But be sure to wrap the parts of your extension code made visible to Python with extern "C" declarations so that they may be called by Pythons C code. For example, to wrap a C++ class, SWIG generates a C++ extension module that declares its initialization function this way, though the rest of the module is pure C++.

The only other potential complication involves C++ static or global object constructor methods when extending. If Python (a C program) is at the top level of a system, such C++ constructors may not be run when the system starts up. This behavior may vary per compiler, but if your C++ objects are not initialized on startup, make sure that your main program is linked by your C++ compiler, not C.

If you are interested in Python/C++ integration in general, be sure to consult the C++ special interest group (SIG) pages at http://www.python.org for information about work in this domain. The CXX system, for instance, makes it easier to extend Python with C++.

Категории