Whole document tree 6 Pointers, Constraints, and Typemaps
IntroductionFor most applications, SWIG's treatment of basic datatypes and pointers is enough to build an interface. However, in certain cases, it is desirable to change SWIG's treatment of particular datatypes. For example, we may want a char ** to act like a list of strings instead of a pointer. In another instance, we may want to tell SWIG that double *result is the output value of a function. Similarly, we might want to map a datatype of float[4] into a 4 element tuple. This chapter describes advanced methods for managing pointers, arrays, and complex datatypes. It also describes how you can customize SWIG to handle new kinds of objects and datatypes.The SWIG Pointer LibraryIf your interface involves C pointers, chances are you will need to work with these pointers in some way or another. The SWIG pointer library provides a collection of useful methods for manipulating pointers. To use the library, simply put the following declaration in your interface file :
or run SWIG as follows :%include pointer.i // Grab the SWIG pointer library
Doing so adds a collection of pointer manipulation functions that are described below. The functions are mainly designed to work with basic C datatypes, but can often be used with more complicated structures.swig -perl5 -lpointer.i interface.i Pointer Library Functionsptrcreate(type,?value?,?nitems?)
ptrfree(ptr)
ptrvalue(ptr,?index?,?type?)
A simple exampleSuppose you have the following C function :
To manage the result output, we can write an interface file like this :void add(double a, double b, double *result) { *result = a + b; }
Now, let's use the pointer library (shown for a few languages) :%module example %include pointer.i extern void add(double a, double b, double *result);
In this case, the idea is simple--we create a pointer, pass it to our C function, and dereference it to get the result. It's essentially identical to how we would have done it in C (well, minus the function call to dereference the value).# Tcl set result [ptrcreate double] ;# Create a double add 4.5 3 $result ;# Call our C function puts [ptrvalue $result] ;# Print out the result ptrfree $result ;# Destroy the double # Perl5 use example; package example; # Functions are in example package $result = ptrcreate("double"); # Create a double add(4.5,3,$result); # Call C function print ptrvalue($result),"\n"; # Print the result ptrfree($result); # Destroy the double # Python import example result = example.ptrcreate("double") # Create a double example.add(4.5,3,result) # Call C function print example.ptrvalue(result) # Print the result example.ptrfree(result) # Destroy the double Creating arraysNow suppose you have a C function involving arrays :
This is also easily handled by our pointer library. For example (in Python) :void addv(double a[], double b[], double c[], int nitems) { int i; for (i = 0; i < nitems; i++) { c[i] = a[i]+b[i]; } }
This example may look quite inefficient on the surface (due to the translation of Python lists to and from C arrays). However, if you're working with lots of C functions, it's possible to simply pass C pointers around between them without any translation. As a result, applications can run fast--even when controlled from a scripting language. It's also worth emphasizing that the ptrcreate() function created a real C array that can be interchanged with other arrays. The ptrvalue() function can also dereference a C pointer even if it wasn't created from Python.# Python function to turn a list into an "array" def build_array(l): nitems = len(l) a = ptrcreate("double",0,nitems) i = 0 for item in l: ptrset(a,item,i) i = i + 1 return a # Python function to turn an array into list def build_list(a,nitems): l = [] for i in range(0,nitems): l.append(ptrvalue(a,i)) return l # Now use our functions a = listtoarray([0.0,-2.0,3.0,9.0]) b = build_array([-2.0,3.5,10.0,22.0]) c = ptrcreate("double",0,4) # For return result add(a,b,c,4) # Call our C function result = build_list(c) # Build a python list from the result print result ptrfree(a) ptrfree(b) ptrfree(c) Packing a data structureThe pointer library can even be used to pack simple kinds of data-structures, perhaps for sending across a network, or simply for changing the value. For example, suppose you had this data structure:
struct Point { double x,y; int color; }; You could write a Tcl function to set the fields of the structure as follows :
proc set_point { ptr x y c } { set p [ptrcast $ptr "double *"] ;# Make a double * ptrset $p $x ;# Set x component set p [ptradd $p 1] ;# Update pointer ptrset $p $y ;# Set y component set p [ptrcast [ptradd $p 1] "int *"] ;# Update pointer and cast ptrset $p $c ;# Set color component } This function could be used even if you didn't tell SWIG anything about the "Point" structure above. Introduction to typemapsSometimes it's desirable to change SWIG behavior in some manner. For example, maybe you want to automatically translate C arrays to and from Perl lists. Or perhaps you would like a particular function argument to behave as an output parameter. Typemaps provide a mechanism for doing just this by modifying SWIG's code generator. Typemaps are new to SWIG 1.1, but it should be emphasized that they are not required to build an interface.The idea (in a nutshell)The idea behind typemaps is relatively simple--given the occurrence of a particular C datatype, we want to apply rules for special processing. For example, suppose we have a C function like this :
void add(double a, double b, double *result) { *result = a + b; } It is clear to us that the result of the function is being returned in the result parameter. Unfortunately, SWIG isn't this smart--after all "result" is just like any other pointer. However, with a typemap, we can make SWIG recognize "double *result" as a special datatype and change the handling to do exactly what we want.
So, despite being a common topic of discussion on the SWIG mailing list, a typemap is really just a special processing rule that is applied to a particular datatype. Each typemap relies on two essential attributes--a datatype and a name (which is optional). When trying to match parameters, SWIG looks at both attributes. Thus, special processing applied to a parameter of "double *result" will not be applied to "double *input". On the other hand, special processing defined for a datatype of "double *" could be applied to both (since it is more general). Using some typemapsIt is easy to start using some typemaps right away. To wrap the above function, simply use the typemaps.i library file (which is part of the SWIG library) as follows :
The %apply directive tells SWIG that we are going to apply special processing to a datatype. The "double *OUTPUT" is the name of a rule describing how to return an output value from a "double *" (this rule is defined in the file typemaps.i). The rule gets applied to all of the datatypes listed in curly braces-- in this case "double *result".// Simple example using typemaps %module example %include typemaps.i // Grab the standard typemap library %apply double *OUTPUT { double *result }; extern void add(double a, double b, double *result);
While it may sound complicated, when you compile the module and use it, you get a function that works as follows :
Our function is much easier to use and it is no longer necessary to create a special double * object and pass it to the function. Typemaps took care of this automatically.# Perl code to call our add function $a = add(3,4); print $a,"\n"; 7 Managing input and output parametersBy default, when SWIG encounters a pointer, it makes no assumptions about what it is (well, other than the fact that it's a pointer). The typemaps.i library file contains a variety of methods for changing this behavior. The following methods are available in this file :Input MethodsThese methods tell SWIG that a pointer is a single input value. When used, functions will expect values instead of pointers.
int *INPUT short *INPUT long *INPUT unsigned int *INPUT unsigned short *INPUT unsigned long *INPUT double *INPUT float *INPUT Suppose you had a C function like this :
You could wrap it with SWIG as follows :double add(double *a, double *b) { return *a+*b; }
Now, when you use your function ,it will work like this :%module example %include typemaps.i ... extern double add(double *INPUT, double *INPUT);
% set result [add 3 4] % puts $result 7 Output MethodsThese methods tell SWIG that pointer is the output value of a function. When used, you do not need to supply the argument when calling the function, but multiple return values can be returned.
These methods can be used as shown in an earlier example. For example, if you have this C function :int *OUTPUT short *OUTPUT long *OUTPUT unsigned int *OUTPUT unsigned short *OUTPUT unsigned long *OUTPUT double *OUTPUT float *OUTPUT
void add(double a, double b, double *c) { *c = a+b; } A SWIG interface file might look like this :
In this case, only a single output value is returned, but this is not a restriction. For example, suppose you had a function like this :%module example %include typemaps.i ... extern void add(double a, double b, double *OUTPUT);
When declared in SWIG as :// Returns a status code and double int get_double(char *str, double *result);
The function would return a list of output values as shown for Python below :as follows :int get_double(char *str, double *OUTPUT);
>>> get_double("3.1415926") # Returns both a status and value [0, 3.1415926] >>> Input/Output MethodsWhen a pointer serves as both an input and output value you can use the following methods :
A typical C function would be as follows :int *BOTH short *BOTH long *BOTH unsigned int *BOTH unsigned short *BOTH unsigned long *BOTH double *BOTH float *BOTH
To make x function as both and input and output value, declare the function like this in an interface file :void negate(double *x) { *x = -(*x); }
Now within a script, you can simply call the function normally :%module example %include typemaps.i ... extern void negate(double *BOTH);
$a = negate(3); # a = -3 after calling this Using different namesBy explicitly using the parameter names of INPUT, OUTPUT, and BOTH in your declarations, SWIG performs different operations. If you would like to use different names, you can simply use the %apply directive. For example :
%apply only renames the different type handling rules. You can use it to match up with the naming scheme used in a header file and so forth. To later clear a naming rule, the %clear directive can be used :// Make double *result an output value %apply double *OUTPUT { double *result }; // Make Int32 *in an input value %apply int *INPUT { Int32 *in }; // Make long *x both %apply long *BOTH {long *x};
%clear double *result; %clear Int32 *in, long *x; Applying constraints to input valuesIn addition to changing the handling of various input values, it is also possible to apply constraints. For example, maybe you want to insure that a value is positive, or that a pointer is non-NULL. This can be accomplished including the constraints.i library file (which is also based on typemaps).Simple constraint exampleThe constraints library is best illustrated by the following interface file :
The behavior of this file is exactly as you would expect. If any of the arguments violate the constraint condition, a scripting language exception will be raised. As a result, it is possible to catch bad values, prevent mysterious program crashes and so on.// Interface file with constraints %module example %include constraints.i double exp(double x); double log(double POSITIVE); // Allow only positive values double sqrt(double NONNEGATIVE); // Non-negative values only double inv(double NONZERO); // Non-zero values void free(void *NONNULL); // Non-NULL pointers only Constraint methodsThe following constraints are currently available
POSITIVE Any number > 0 (not zero) NEGATIVE Any number < 0 (not zero) NONNEGATIVE Any number >= 0 NONPOSITIVE Any number <= 0 NONZERO Nonzero number NONNULL Non-NULL pointer (pointers only). Applying constraints to new datatypesThe constraints library only supports the built-in C datatypes, but it is easy to apply it to new datatypes using %apply. For example :
The special types of "Number" and "Pointer" can be applied to any numeric and pointer variable type respectively. To later remove a constraint, the %clear directive can be used :// Apply a constraint to a Real variable %apply Number POSITIVE { Real in }; // Apply a constraint to a pointer type %apply Pointer NONNULL { Vector * };
%clear Real in; %clear Vector *; Writing new typemapsSo far, we have only seen a high-level picture of typemaps and have utilized pre-existing typemaps in the SWIG library. However, it is possible to do more if you're willing to get your hands dirty and dig into the internals of SWIG and your favorite scripting language.
Before diving in, first ask yourself do I really need to change SWIG's default behavior? The basic pointer model works pretty well most of the time and I encourage you to use it--after all, I wanted SWIG to be easy enough to use so that you didn't need to worry about low level details. If, after contemplating this for awhile, you've decided that you really want to change something, a word of caution is in order. Writing a typemap from scratch usually requires a detailed knowledge of the internal workings of a particular scripting language. It is also quite easy to break all of the output code generated by SWIG if you don't know what you're doing. On the plus side, once a typemap has been written it can be reused over and over again by putting it in the SWIG library (as has already been demonstrated). This section describes the basics of typemaps. Language specific information (which can be quite technical) is contained in the later chapters. Motivations for using typemapsSuppose you have a few C functions such as the following :
In this case, the third argument takes a 4 element array. If you do nothing, SWIG will convert the last argument into a pointer. When used in the scripting language, you will need to pass a "GLfloat *" object to the function to make it work.void glLightfv(GLenum light, Glenum pname, GLfloat parms[4]); Managing special data-types with helper functionsHelper functions provide one mechanism for dealing with odd datatypes. With a helper function, you provide additional functionality for creating and destroying objects or converting values into a useful form. These functions are usually just placed into your interface file with the rest of the functions. For example, a few helper functions to work with 4 element arrays for the above function, might look like this :
%inline %{ /* Create a new GLfloat [4] object */ GLfloat *newfv4(double x, double y, double z, double w) { GLfloat *f = (GLfloat *) malloc(4*sizeof(GLfloat)); f[0] = x; f[1] = y; f[2] = z; f[3] = w; return f; } /* Destroy a GLfloat [4] object */ void delete_fv4(GLfloat *d) { free(d); } %} When wrapped, our helper functions will show up the interface and can be used as follows :
% set light [newfv4 0.0 0.0 0.0 1.0] # Creates a GLfloat * % glLightfv GL_LIGHT0 GL_AMBIENT $light # Pass it to the function ... % delete_fv4 $light # Destroy it (When done)
While not the most elegant approach, helper functions provide a simple mechanism for working with more complex datatypes. In most cases, they can be written without diving into SWIG's internals. Before typemap support was added to SWIG, helper functions were the only method for handling these kinds of problems. The pointer.i library file described earlier is an example of just this sort of approach. As a rule of thumb, I recommend that you try to use this approach before jumping into typemaps. A Typemap ImplementationAs we have seen, a typemap can often eliminate the need for helper functions. Without diving into the details, a more sophisticated typemap implementation of the previous example can permit you to pass an array or list of values directly into the C function like this :
This is a more natural implementation that replaces the low-level pointer method. Now we will look into how one actually specifies a typemap.% glLightfv GL_LIGHT0 GL_AMBIENT {0.0 0.0 0.0 1.0} What is a typemap?A typemap is specified using the %typemap directive in your interface file. A simple typemap might look liks this :
%module example %typemap(tcl,in) int { $target = atoi($source); printf("Received an integer : %d\n", $target); } int add(int a, int b); In this case, we changed the processing of integers as input arguments to functions. When used in a Tcl script, we would get the following debugging information:
In the typemap specification, the symbols $source and $target are holding places for C variable names that SWIG is going to use when generating wrapper code. In this example, $source would contain a Tcl string containing the input value and $target would be the C integer value that is going to be passed into the "add" function.% set a [add 7 13] Received an integer : 7 Received an integer : 13 Creating a new typemapA new typemap can be created as follows :
lang specifies the target language, method defines a particular conversion method, and Datatype gives the corresponding C datatype. The code corresponding to the typemap is enclosed in braces after the declaration. There are about a dozen different kinds of typemaps that are used within SWIG, but we will get to that shortly.%typemap(lang,method) Datatype { ... Conversion code ... }
A single conversion can be applied to multiple datatypes by giving a comma separated list of datatypes. For example :
Here, $type will be expanded into the real datatype during code generation. Datatypes may also carry names as in%typemap(tcl,in) int, short, long, signed char { $target = ($type) atol($source); }
A "named"typemap will only apply to an object that matches both the C datatype and the name. Thus the char **argv typemap will only be applied to function arguments that exactly match "char **argv". In some cases, the name may correspond to a function name (as is the case for return values).%typemap(perl5,in) char **argv { ... Turn a perl array into a char ** ... }
Finally, there is a shortened form of the typemap directive :
When the language name is ommitted, the typemap will be applied to the current target language. This form is only recommended for typemap methods that are language independent (there are a few). It is not recommended if you are building interfaces for multiple languages.%typemap(method) Datatype { ... } Deleting a typemapA typemap can be deleted by providing no conversion code. For example :
%typemap(lang,method) Datatype; // Deletes this typemap Copying a typemapA typemap can be copied using the following declaration :
This specifies that the typemap for "unsigned int" should be the same as the "int" typemap.%typemap(python,out) unsigned int = int; // Copies a typemap This is most commonly used when working with library files. Typemap matching rulesWhen you specify a typemap, SWIG is going to try and match it with all future occurrences of the datatype you specify. The matching process is based upon the target language, typemap method, datatype, and optional name. Because of this, it is perfectly legal for multiple typemaps to exist for a single datatype at any given time. For example :
These typemaps all involve the "int *" datatype in one way or another, but are all considered to be distinct. There is an extra twist to typemaps regarding the similarity between C pointers and arrays. A typemap applied to a pointer will also work for any array of the same type. On the other hand, a typemap applied to an array will only work for arrays, not pointers. Assuming that you're not completely confused at this point, the following rules are applied in order to match pointers and arrays :%typemap(tcl,in) int * { ... Convert an int * ... } %typemap(tcl,in) int [4] { ... Convert an int[4] ... } %typemap(tcl,in) int out[4] { ... Convert an out[4] ... } %typemap(tcl,in) int *status { ... Convert an int *status ... }
The following interface file shows how these rules are applied.
Because SWIG uses a name-based approach, it is possible to attach special properties to named parameters. For example, we can make an argument of "int *OUTPUT" always be treated as an output value of a function or make a "char **argv" always accept a list of string values.void foo1(int *); // Apply int * typemap void foo2(int a[4]); // Apply int[4] typemap void foo3(int out[4]); // Apply int out[4] typemap void foo4(int *status); // Apply int *status typemap void foo5(int a[20]); // Apply int * typemap (because int [20] is an int *) Common typemap methodsThe following methods are supported by most SWIG language modules. Individual language may provide any number of other methods not listed here.
![]() ![]() Understanding how some of these methods are applied takes a little practice and better understanding of what SWIG does when it creates a wrapper function. The next few diagrams show the anatomy of a wrapper function and how the typemaps get applied. More detailed examples of typemaps can be found on the chapters for each target language.
![]()
![]() Writing typemap codeThe conversion code supplied to a typemap needs to follow a few conventions described here.ScopeTypemap code is enclosed in braces when it is inserted into the resulting wrapper code (using C's block-scope). It is perfectly legal to declare local and static variables in a typemap. However, local variables will only exist in the tiny portion of code you supply. In other words, any local variables that you create in a typemap will disappear when the typemap has completed its execution.Creating local variablesSometimes it is necessary to declare a new local variable that exists in the scope of the entire wrapper function. This can be done by specifying a typemap with parameters as follows :
What happens here is that temp becomes a local variable in the scope of the entire wrapper function. When we set it to a value, that values persists for the duration of the wrapper function and gets cleaned up automatically on exit. This is particularly useful when working with pointers and temporary values.%typemap(tcl,in) int *INPUT(int temp) { temp = atoi($source); $target = &temp; }
It is perfectly safe to use multiple typemaps involving local variables in the same function. For example, we could declare a function as :
When this occurs, SWIG will create three different local variables named `temp'. Of course, they don't all end up having the same name---SWIG automatically performs a variable renaming operation if it detects a name-clash like this.void foo(int *INPUT, int *INPUT, int *INPUT);
Some typemaps do not recognize local variables (or they may simply not apply). At this time, only the "in", "argout", "default", and "ignore" typemaps use local variables. Special variablesThe following special variables may be used within a typemap conversion code :
![]() When found in the conversion code, these variables will be replaced with the correct values. Not all values are used in all typemaps. Please refer to the SWIG reference manual for the precise usage. Typemaps for handling arraysOne of the most common uses of typemaps is providing some support for arrays. Due to the subtle differences between pointers and arrays in C, array support is somewhat limited unless you provide additional support. For example, consider the following structure appears in an interface file :
When SWIG is run, you will get the following warnings :struct Person { char name[32]; char address[64]; int id; };
swig -python example.i Generating wrappers for Python example.i : Line 2. Warning. Array member will be read-only. example.i : Line 3. Warning. Array member will be read-only. These warning messages indicate that SWIG does not know how you want to set the name and address fields. As a result, you will only be able to query their value.
To fix this, we could supply two typemaps in the file such as the following :
The "memberin" typemap is used to set members of structures and classes. When you run the new version through SWIG, the warnings will go away and you can now set each member. It is important to note that char[32] and char[64] are different datatypes as far as SWIG typemaps are concerned. However, both typemaps can be combined as follows :%typemap(memberin) char [32] { strncpy($target,$source,32); } %typemap(memberin) char [64] { strncpy($target,$source,64); }
The ANY keyword can be used in a typemap to match any array dimension. When used, the special variable $dim0 will contain the real dimension of the array and can be used as shown above.// A better typemap for char arrays %typemap(memberin) char [ANY] { strncpy($target,$source,$dim0); }
Multidimensional arrays can also be handled by typemaps. For example :
When multi-dimensional arrays are used, the symbols $dim0, $dim1, $dim2, etc... get replaced by the actual array dimensions being used.// A typemap for handling any int [][] array %typemap(memberin) int [ANY][ANY] { int i,j; for (i = 0; i < $dim0; i++) for (j = 0; j < $dim1; j++) { $target[i][j] = *($source+$dim1*i+j); } }
The ANY keyword can be combined with any specific dimension. For example :
A typemap using a specific dimension always has precedence over a more general version. For example, [ANY][4] will be used before[ANY][ANY].%typemap(python,in) int [ANY][4] { ... } Typemaps and the SWIG LibraryWriting typemaps is a tricky business. For this reason, many common typemaps can be placed into a SWIG library file and reused in other modules without having to worry about nasty underlying details. To do this, you first write a file containing typemaps such as this :
This file may contain dozens or even hundreds of possible mappings. Now, to use this file with other modules, simply include it in other files and use the %apply directive :// file : stdmap.i // A file containing a variety of useful typemaps %typemap(tcl,in) int INTEGER { ... } %typemap(tcl,in) double DOUBLE { ... } %typemap(tcl,out) int INT { ... } %typemap(tcl,out) double DOUBLE { ... } %typemap(tcl,argout) double DOUBLE { ... } // and so on...
// interface.i // My interface file %include stdmap.i // Load the typemap library // Now grab the typemaps we want to use %apply double DOUBLE {double}; // Rest of your declarations In this case, stdmap.i contains a variety of standard mappings. The %apply directive lets us apply specific versions of these to new datatypes without knowing the underlying implementation details.
To clear a typemap that has been applied, you can use the %clear directive. For example :
%clear double x; // Clears any typemaps being applied to double x Implementing constraints with typemapsOne particularly interesting application of typemaps is the implementation of argument constraints. This can be done with the "check" typemap. When used, this allows you to provide code for checking the values of function arguments. For example :
This provides a sanity check to your wrapper function. If a negative number is passed to this function, a Perl exception will be raised and your program terminated with an error message.%module math %typemap(perl5,check) double *posdouble { if ($target < 0) { croak("Expecting a positive number"); } } ... double sqrt(double posdouble);
This kind of checking can be particularly useful when working with pointers. For example :
will prevent any function involving a Vector * from accepting a NULL pointer. As a result, SWIG can often prevent a potential segmentation faults or other run-time problems by raising an exception rather than blindly passing values to the underlying C/C++ program.%typemap(python,check) Vector * { if ($target == 0) { PyErr_SetString(PyExc_TypeError,"NULL Pointer not allowed"); return NULL; } } Typemap examplesTypemaps are inherently language dependent so more examples appear in later chapters. The SWIG Examples directory also includes a variety of examples. Sophisticated users may gain more by examining the typemaps.i and constraints.i SWIG library files.How to break everything with a typemapIt should be emphasized that typemaps provide a direct mechanism for modifying SWIG's output. As a result, it can be very easy to break almost everything if you don't know what you're doing. For this reason, it should be stressed that typemaps are not required in order to use SWIG with most kinds of applications. Power users, however, will probably find typemaps to be a useful tool for creating extremely powerful scripting language extensions.Typemaps and the futureThe current typemap mechanism, while new, will probably form the basis of SWIG 2.0. Rather than having code buried away inside a C++ module, it may soon be possible to redefine almost all of SWIG's code generation on the fly. Future language modules will rely upon typemaps almost exclusively.
SWIG 1.1 - Last Modified : Mon Aug 4 10:46:56 1997 |