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:
- A C++ coded Python extension module with accessor functions that interface with the C++ classs methods and members
- A Python coded wrapper class (called a "shadow" class in SWIG-speak) that interfaces with the C++ class accessor functions module
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:
- number_wrap.doc, a simple wrapper function description file
- number_wrap.c, a C++ extension module with class accessor functions
- number.py, a Python shadow class module that wraps accessor functions
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 " 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.
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.
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.
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.
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
( 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.
.8.3 Using the C++ Class in Python
Example 19-24. PP2EIntegrateExtendSwigShadowmain.py
Example 19-25. PP2EIntegrateExtendSwigShadowmain_low.py
Example 19-26. PP2EIntegrateExtendSwigShadowmain_subclass.py
Категории