Copyright (C) 1998
All Rights Reserved
To answer the question of why one might want to use Tcl for this purpose, consider the features that Tcl provides. First, Tcl provides an interpreted environment that allows you interactively control and manipulate the underlying C program. In other words, a Tcl interface allows you to call C functions, examine variables, and perform tasks that might otherwise be found in a debugger. Second, Tcl provides a high-level programming language that can be used to write programs, rapidly prototype new features, develop testing scripts, and interact with C code in a highly flexible manner. And finally, one shouldn't forget that Tcl allows you to utilize other extensions and modules people have developed (for example, you could later write a graphical user interface in Tk, access database systems with OraTcl, and so forth). In short, Tcl provides a number of attractive features that simply aren't found in a typical C/C++ development environment.
In this chapter, I describe SWIG (Simplified Wrapper and Interface Generator), a development tool that integrates C/C++ code with Tcl and other scripting languages. SWIG is a compiler that can be used to automatically generate Tcl extensions to existing C code. It requires no modifications to the underlying code and provides access to most C programming features including pointers, arrays, structures, classes, and so forth. Unlike traditional Tcl extension programming, SWIG automatically generates extensions and hides almost all of the underlying implementation details. As a result, SWIG makes it easy to use Tcl in a rapidly changing C/C++ development environment--even if the underlying C/C++ code does not involve Tcl.
SWIG is freely available from www.swig.org and supports Tcl on Unix, Windows-NT, and Macintosh platforms. SWIG also generates Perl and Python extensions and many of the features described in this chapter also apply to those languages. Finally, SWIG is packaged with more than 300 pages of tutorial-style documentation. It is not my intent to repeat that documentation here although there will be some unavoidable overlap. Rather, the goal is to describe how SWIG works, what it does, and how it can be used as a C/C++ development tool.
To make this new command available to Tcl, you also need to write an initialization function such as follows:/* PowObjCmd * * Tcl 8.x wrapper for the pow(x,y) function. */ #include <math.h> int PowObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { Tcl_Obj *resultptr; double x,y,result; int error; if (objc != 3) { Tcl_WrongNumArgs(interp,2,objv, "Usage : pow x y"); return TCL_ERROR; } error = Tcl_GetDoubleFromObj(interp, objv[1], &x); if (error != TCL_OK) return error; error = Tcl_GetDoubleFromObj(interp, objv[2], &y); if (error != TCL_OK) return error; result = pow(x,y); resultptr = Tcl_GetObjResult(interp); Tcl_SetDoubleObj(resultptr,result); return TCL_OK; }
Finally, to build the Tcl extension, these functions (and the original C functions) are compiled into an extension module. In newer versions of Tcl, this is usually done by compiling the extension module into a shared library or DLL that can be dynamically loaded into the Tcl interpreter. In this case, using the module from Tcl might look like this :int Example_Init(Tcl_Interp *interp) { Tcl_CreateObjCommand(interp, "pow", PowObjCmd, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); return TCL_OK; }
In cases where dynamic loading is not supported, extension modules can be compiled into the tclsh or wish executable directly (also known as static linking). This is accomplished by writing a Tcl_AppInit() function and a main() function.tclsh % load ./example.so Example % pow 2 3 8.0 %
Although writing a simple Tcl extension may be easy enough, consider the problem of providing a Tcl interface to a large C/C++ library containing hundreds of functions, classes, objects, variables, and constants. In this case, it would be necessary to write hundreds of complicated wrapper functions---a time consuming task to be certain. Not only that, providing a mapping between C and Tcl is further complicated by data representation issues and object management (i.e. how do you manage C/C++ objects, pointers, arrays, etc...). To make matters worse, consider the use of Tcl in a rapidly changing C/C++ application development environment where APIs are evolving, packages might only be partially implemented, and designs are being revised. Needless to say, it is difficult to write and maintain Tcl wrappers in this setting. Given the choice, most developers would rather concentrate on the job at hand--not the task of writing Tcl wrapper code. As a result, the use of Tcl might be ignored entirely or only considered much later in a project (after the code has stabilized)./* main.c */ #include <tcl.h> int Tcl_AppInit(Tcl_Interp *interp); int main(int argc, char *argv[]) { Tcl_Main(argc, argv, Tcl_AppInit); } int Tcl_AppInit(Tcl_Interp *interp) { /* Initialize Tcl */ if (Tcl_Init(interp) == TCL_ERROR) { return TCL_ERROR; } /* Initialize our extension */ if (Example_Init(interp) == TCL_ERROR) { return TCL_ERROR; } return TCL_OK; }
In this file, the functionality of the Tcl module is defined entirely with ANSI C prototypes. Instead of writing wrapper code, you only need to list the functions that you want to access. In the first part of the file, the %module directive names the extension module. The %{, %} section is used to enclose code that should be copied into the output wrapper code (usually this is used to grab the right header files and provide additional support code when needed).// example.i : A Simple SWIG interface %module example %{ #include <math.h> %} // ANSI C/C++ prototypes double pow(double x, double y);
To build the extension, you simply run SWIG on this file to generate the Tcl wrapper code (in this case SWIG creates a file called example_wrap.c). This file is then compiled into a Tcl extension module exactly as before. For example (shown for Linux) :
With SWIG, the extension module works exactly as expected--however, you didn't have to know anything about Tcl other than knowing how to compile and link the final extension module.> swig -tcl8 example.i Generating wrappers for Tcl 8.x > gcc -fpic -c example_wrap.c > gcc -shared example_wrap.o -o example.so -lm > tclsh % load ./example.so example % pow 2 3 8.0 %
Unlike normal Tcl extension building, SWIG extensions can be very easy to generate. In the example, you could easily make an interface to the entire <math.h> library by simply copying all of the function prototypes out of the header file and placing them in the interface. If you later wanted to change the Tcl interface, you would simply make these changes to the interface file. The resulting Tcl interface would then be updated when the extension module was recompiled.
At this point, you know about 90% of what you need to know to start using SWIG (and the simple example given here can even be used as a starting point for your own applications). The key thing to keep in mind is that SWIG is entirely automatic and it uses an extended subset of ANSI C syntax. Modules can usually be created by simply massaging existing header files and other sources of ANSI C prototypes. In fact, many users are surprised to find out how easy it can be to build Tcl extensions in this manner (in fact, one early SWIG user was able to wrap the entire OpenGL library into a Tcl module with less than ten minutes of effort!).
SWIG is designed to be extensible with new language modules and documentation methods. In addition to producing wrappers for Tcl, SWIG can be used for interfacing with a variety of other languages including Perl, Python, and Guile (in fact, users have even developed modules for Java and commercial packages such as MATLAB).
SWIG was originally developed at Los Alamos National Laboratory as a development tool for integrating physics applications with scripting languages. With this in mind, there were a number of design goals :
The separation between the interface description and wrapper code is one of SWIG's most important features. First, it allows Tcl to be used with ordinary C/C++ code--in fact, this code doesn't need to know anything about the Tcl interface. Second, this allows for rapid change and development. If the C code changes in some manner, the Tcl interface can be easily regenerated to reflect the new implementation. It is also easy to build a stand-alone C application, switch scripting languages, or even upgrade to newer versions of Tcl (for example, switching from Tcl 7.6 to Tcl 8.0). Finally, SWIG shifts the focus from the development of Tcl wrapper functions to the problem at hand. As a result, it becomes possible to use Tcl in situations where it might otherwise have not been considered.
swig <options> filename -tcl Generate Tcl 7.x wrappers -tcl8 Generate Tcl 8.x wrappers -perl5 Generate Perl5 wrappers -python Generate Python wrappers -dascii Produce ASCII documentation -dhtml Produce HTML documentation -dlatex Produce LaTeX documentation -c++ Enable C++ mode -objc Enable Objective-C mode -Idir Add a directory to the SWIG search path -lfile Include a SWIG library file -o outfile Set the name of the output file -module name Set the module name -Dsymbol Define a symbol -version Display SWIG's version number -help Display all options
This is only a partial list of options. A full listing of options can be obtained by invoking "swig -help".
When given an input file of `myfile.i', SWIG will produce an output file of `myfile_wrap.c' as well as a documentation file. If necessary, the name of the output file can be changed using the -o option (this is sometimes necessary when working with C++ compilers that expect to see a C++ suffix on source files).
Although SWIG can be invoked directly on the command line, most users find it easier to invoke SWIG within a Makefile. A typical Tcl Makefile might look like the following :
# Sample Makefile for a Tcl extension SRCS = example.c # Source files OBJS = # Object files (already created) INTERFACE = example.i # SWIG Interface file WRAPFILE = $(INTERFACE:.i=_wrap.c) WRAPOBJ = $(INTERFACE:.i=_wrap.o) TARGET = example.so # Output file # Compiler options CC = gcc CFLAGS = INCLUDE = # SWIG Options SWIG = /usr/local/bin/swig SWIGOPT = -tcl # use -tcl8 for Tcl 8.0 SWIGCC = $(CC) # Rules for creating .o files from source. COBJS = $(SRCS:.c=.o) ALLOBJS = $(COBJS) $(OBJS) # Shared library options (Shown for Linux) CCSHARED = -fpic BUILD = $(CC) -shared # Tcl installation (where is Tcl/Tk located) TCL_INCLUDE = -I/usr/local/include TCL_LIB = -L/usr/local/lib # Additional link libraries LIBS = .SUFFIXES: .c .c.o: $(CC) $(CCSHARED) $(CFLAGS) $(INCLUDE) -c $< all: $(TARGET) # Convert the SWIG wrapper file into an object file $(WRAPOBJ) : $(WRAPFILE) $(SWIGCC) -c $(CCSHARED) $(CFLAGS) $(WRAPFILE) $(INCLUDE) \ $(TCL_INCLUDE) # Run SWIG $(WRAPFILE) : $(INTERFACE) $(SWIG) $(SWIGOPT) -o $(WRAPFILE) $(INTERFACE) # Build the final extension module $(TARGET): $(WRAPOBJ) $(ALLOBJS) $(BUILD) $(WRAPOBJ) $(ALLOBJS) $(LIBS) -o $(TARGET)
Windows-NT users can invoke SWIG directly from the MS-DOS command shell, from Makefiles, or as a special processing option in whatever development environment that they are using. For example, in Visual C++, you can invoke SWIG in the same manner as you would invoke other code generation tools such as Lex and Yacc. Consult the documentation for your development environment for more details.
%module mymodule %{ /* Include header files here */ #include "myheader.h" %} // Now list ANSI C declarations extern double foo(double); ...
SWIG also includes a preprocessor that can be used for conditional compilation and macro expansion. This can be used to create mixed interface/header files that are acceptable to both SWIG and the C compiler. For example :
/* example.h : A Mixed SWIG/C file */ #ifdef SWIG %module example %{ #include "example.h" %} #endif #ifdef __STDC__ #define _ANSI_ARGS(a) a #else #define _ANSI_ARGS(a) () #endif #ifdef __cplusplus #define EXTERN extern "C" #else #define EXTERN extern #endif /* Now declare function prototypes */ EXTERN double foo _ANSI_ARGS((double)); ...
In systems that change alot, this approach makes it easier to maintain consistency between header files and interface specifications (even if header files play preprocessor tricks to address system and compiler dependencies).
For the most part, anything that can be given to a C compiler can also be given to SWIG. However, it is important to understand that SWIG is not a full C/C++ compiler. In some cases, it may get confused by extremely complicated declarations. Other features simply aren't supported--for example, variable length arguments, operator overloading, templates, etc... When in doubt, the best thing to do is try it and see. If SWIG complains, you can either remove the offending declaration, comment it out, or use conditional compilation to hide it. However, the SWIG compiler is always improving so current limitations may be eliminated in the future.
These declarations are accessible in Tcl as follows%module example int factorial(int n); extern double My_variable; #define PI 3.1415926 const int SPAM = 42; ...
For C global variables, the Tcl interpreter only supports a small subset of C datatypes (int, double, and char *). When other datatypes are used such as the following,% puts [factorial 4] ;# Call a C function 24 % set My_variable 3.4 ;# Change a C global variable % puts $My_variable ;# Read a C global variable 3.4 % puts $PI ;# Output the value of a C constant 3.1415926 % puts $SPAM 42 %
short My_short;
SWIG creates a pair of accessor functions as follows :
puts [My_short_get] ;# Get value of a global variable My_short_set 5.5 ;# Set value of a global variable
Pointers may appear throughout a SWIG interface file. Furthermore, type definitions are not generally required. For example :_100f8e2_Vector_p
In Tcl, pointers are opaque objects that can be passed around between different C functions and operate like you would expect. For example :%module vector %{ #include "vector.h" %} Vector *new_vector(double x, double y, double z); double dot_product(Vector *a, Vector *b); ...
For the most part, pointers in Tcl work in the same way as they do in C. However, the primary difference is that pointers can't be dereferenced in Tcl. In other words, you can't peer inside objects and manipulate them (well, at least not without a little help).% set v1 [new_Vector 2 3 4] # Create two vectors % set v2 [new_Vector 5 6 7] % puts $v1 _100f8e2_Vector_p % dot_product $v1 $v2 56 %
Interestingly enough, this approach avoids a number of difficult problems related to data representation and working with multiple languages. By supporting pointers, it is not necessary for SWIG or Tcl to understand the underlying implementation of C/C++ objects. In fact, SWIG does not need the definition of complex objects in order to use them. For example, the Vector type was used above, but the definition of Vectors was never specified in the interface file.
Finally, SWIG uses the pointer type-signature to perform type-checking. Whenever a pointer is passed to a C function, its type is checked against an expected value. If a mismatch occurs, the pointer will be rejected and a Tcl error raised. For example :
% set v1 [new_vector 2 3 4] % set m [new_matrix 10 10] % dot_product $v1 $m Type error in argument 2 of dot_product. Expected _Vector_p.
With type checking, it is safe to pass pointers around in Tcl much in the same way as would be done in C/C++ (which may be a good or bad thing depending on your point of view--or all bad if you're a language purist).
is translated into the following collection of C functions :struct Vector { double x,y,z; };
Accessor functions simply take an object as the first argument and provide a way to manipulate its internal representation from Tcl. For exampledouble Vector_x_get(struct Vector *obj) { return obj->x; } double Vector_x_set(struct Vector *obj, double x) { return (obj->x = x); } double Vector_y_get(struct Vector *obj) { return obj->y; } double Vector_y_set(struct Vector *obj, double y) { return (obj->y = y); } double Vector_z_get(struct Vector *obj) { return obj->z; } double Vector_z_set(struct Vector *obj, double z) { return (obj->z = z); }
Similar access is provided for unions and the data members of C++ classes.# v is a Vector that got created somehow % Vector_x_get $v 3.5 % Vector_x_set $v 7.8 ;# Change x component
For C++ class definitions, the same technique is used to provide access to member functions, constructors, and destructors. For example,
class List { public: List(); ~List(); int search(char *item); void insert(char *item); void remove(char *item); char *get(int n); int length; static void print(List *l); };
is translated into the following functions :
Within Tcl, you can use the functions as follows :List *new_List() { return new List; }; void delete_List(List *l) { delete l; } int List_search(List *l, char *item) { return l->search(item); } void List_insert(List *l, char *item) { l->insert(item); } void List_remove(List *l, char *item) { l->remove(item); } char *List_get(List *l, int n) { return l->get(n); } int List_length_get(List *l) { return l->length; } int List_length_set(List *l, int n) { return (l->length = n); } void List_print(List *l) { List::print(l); }
% set l [new_List] % List_insert $l Ale % List_insert $l Stout % List_insert $l Lager % List_print $l Lager Stout Ale % puts [List_length_get $l] 3 % puts $l _1008560_List_p %
While somewhat primitive, the low-level SWIG interface provides direct and flexible access to almost any C++ object. As it turns out, it is not necessary to provide SWIG with the complete definition of a structure or class. Access can be restricted by only providing a limited definition (or no definition at all).
For C++, SWIG also supports inheritance (including multiple inheritance). Inheritance hierarchies are encoded into the run-time pointer type-checker and work as you would expect. For example, suppose you had the following classes :
In the Tcl interface, "Circle" and "Square" objects would be accepted any place a "Shape" is expected. However, the type system does not allow situations that are illegal (or problematic) in C++. For example :class Shape { public: virtual double area() = 0; virtual double perimeter() = 0; }; class Circle : public Shape { public: Circle(double radius); ~Circle(); double area(); double perimeter(); }; class Square : public Shape { public: Square(double width); ~Square(); double area(); double perimeter(); };
tclsh % set c [new_Circle 4] ;# Create a circle % set s [new_Square 10] ;# Create a square % Square_area $a ;# Use derived class 100.0 % Shape_area $s ;# Use base class 100.0 % Shape_perimeter $c 25.1327412287 % Square_area $c # Try to violate the type system Type error in argument 1 of Square_area. Expected _Square_p.
For many users, this provides a much more natural interface to C/C++ objects. It also looks rather familiar to what is done in Tcl extensions such as [incr Tcl]. Even though this interface provides an alternative mechanism for managing objects, it is still closely related to the pointer handling mechanism already described. In particular, it may be necessary to extract a pointer value from a Tcl object. This can be done by extracting the `this' value from an object as follows:% List l ;# Create a new list % l insert Ale ;# Invoke some member functions % l insert Stout % l insert Lager % List_print [l cget -this] Lager Stout Ale % puts [l cget -length] ;# Get the length 3 % puts [l cget -this] ;# Get the `this' pointer _1008560_List_p % rename l "" ;# Delete l
In other cases, it may be useful to turn an existing pointer into a Tcl object. This can be done as follows :set t [l cget -this] # Extract the pointer value
List l -this $t # Turn a pointer into an object
Although the object interface provides a "natural" interface to objects, it also has a number of limitations. First, it can result in a substantial amount of additional wrapper code. If wrapping hundreds of structures and classes, the size of the module created by SWIG can be quite large. If this is a concern, the object interface can be disabled by running SWIG with the `-noobject' option. Second, SWIG does not make Tcl object-oriented in the same sense as extensions such as [incr Tcl]. For example, you would not be able to inherit from objects wrapped by SWIG. Finally, the object interface can be awkward to use in combination with functions expecting pointers since you will need to convert back and forth between object model and the pointer model. Although this interface won't be described much further, all of the gory details can be found in the SWIG Users Manual.
%name simply changes the name of a command, class, or member function when used in Tcl (it has no effect on the underlying C code).%module example ... %name(mylindex) int lindex(); ... %name(clist) class list { public: ... };
Read-only mode stays in effect until it is explicitly disabled with the %readwrite directive.%readonly double foo; // foo will be read-only int bar; // bar will be read-only %readwrite int spam = 42; // spam is read-write class List { public: ... %readonly int length; // length will be readonly %readwrite ... };
The other use of %include is to retrieve files from the SWIG library--a repository of common modules and extensions that is packaged with the SWIG distribution. For example, if you wanted to relink the tclsh interpreter with a SWIG extension added to it, you can do this :// Interface to OpenGL %module opengl // Include a variety of pieces %include gl.i %include glu.i %include "GL/gluaux.h" %include "test.c"
In this case, the library file provides all of the additional support code (Tcl_AppInit(), main(), etc...) needed to build a new version of tclsh.%module example %{ #include "myheader.h" %} %include tclsh.i // Include Tcl_AppInit() code for tclsh // C declarations
Library files can also be included on the command line using the -l option. For example :
% swig -tcl8 -lwish.i example.i
When files are included in this manner, they are appended to the end of the SWIG input file (i.e. files included on the command line are parsed last).
Code can be inserted into the header section by enclosing it with %{, %} (these are also known as code blocks). We have already seen this used to include the right header files. However, you can also include supporting C functions. For example :
%module mymodule %{ #include "my_header.h" %} ... Declare C functions here // Now include a Tcl_AppInit function (note: the tclsh.i library file // does the same thing). %{ int main(int argc, char **argv) { Tcl_Main(argc, argv, Tcl_AppInit); return(0); } int Tcl_AppInit(Tcl_Interp *interp){ int Mymodule_Init(Tcl_Interp *); if (Tcl_Init(interp) == TCL_ERROR) return TCL_ERROR; /* Now initialize our functions */ if (Mymodule_Init(interp) == TCL_ERROR) return TCL_ERROR; return TCL_OK; } %}
Code blocks are also sometimes used to write helper functions. These are C functions that you want to include in the Tcl interface, but might not be part of the original C library or package. For example :
Since writing helper functions is relatively common, a shorthand form of the above is available using the %inline directive as follows :// Include a support function in our module %{ /* Create a new vector */ static Vector *new_Vector() { return (Vector *) malloc(sizeof(Vector)); } %} // Now tell SWIG about it. Vector *new_Vector();
The %inline directive is really just a convenient way to write new C/C++ functions that you would like to include in your Tcl interface.%inline %{ /* Create a new vector */ Vector *new_Vector() { return (Vector *) malloc(sizeof(Vector)); } %}
If you want to insert code into the module initialization function, you can do the following :
%init %{ init_module(); // Perform module-specific initialization %}
This code will be executed when Tcl loads or initializes your extension module and can be used to initialize your application.
Finally, there is a %wrapper directive for including code into the wrapper section of SWIG's output. This is rarely used (or necessary) but is available for special cases.
As is, there is no way to create, destroy, or look at Vectors in a convenient manner. However, with SWIG, you can extend the Vector class with some new methods as follows :struct Vector { double x, y, z; };
Now, in Tcl, you can do the following :%module vector %{ #include "vector.h" %} struct Vector { double x,y,z; }; // Now extend the Vector structure with some methods %addmethods Vector { // Create a new vector Vector(double x, double y, double z) { Vector *v = (Vector *) malloc(sizeof(Vector)); v->x = x; v->y = y; v->z = z; return z; } // Destroy a vector ~Vector() { free(self); } // Print out a vector for debugging void output() { printf("[ %g, %g, %g ]\n", self->x, self->y, self->z); } };
% Vector v 2 5.5 -9 ; # Create a new vector % v output ; # Output it's value [ 2, 5.5, -9 ] % rename v "" ; # Destroy v (calls the destructor)
Thus, with just a little work, you can turn a C structure into something that looks alot like a C++ object with constructors, destructors, and methods. However, this is done without modifying the original C structure or requiring a C++ compiler.
Just as C structures can be extended, C++ classes can also be extended with new methods. The extension process does not modify the original class nor does it rely upon C++ inheritance.
The code you supply is inserted directly into the wrapper code created by SWIG. The `$function' token is simply a placeholder for the actual C/C++ function call. The exception handler remains in effect under it is redefined or disabled. Exceptions can be disabled by simply omitting the code. For example :// Code for catching a C++ exception %except(tcl) { try { $function // The real function call is placed here } catch (RangeError) { interp->result = "Out of bounds."; return TCL_ERROR; } }
%except(tcl);
Exception handling is not limited to C++ exceptions or any particular error handling mechanism. Essentially any valid C/C++ code for checking errors can be used with the %except directive.
SWIG also provides an exception handling library, exception.i, that can be used to handle exceptions in a language-neutral manner. For example :
The benefit of using the exception library is that exception handlers can be easily written once and used by all of the target scripting languages supported by SWIG.// Include the SWIG exception handling library %include exception.i // Specify a generic exception handler (works with all target languages) %except { try { $function } catch (RangeError) { SWIG_exception(SWIG_RuntimeError, "Range Error"); } }
For most users, the default behavior is enough to get started. However, in some cases, it may be useful to change SWIG's handling of a particular datatype (by adding special processing or mapping a datatype into Tcl in a different manner). To support this, SWIG provides a customization mechanism known as "typemaps." In a nutshell, a typemap is a special processing rule that is attached to particular C/C++ datatypes. The following example shows a very simple typemap in action :
When this example is compiled into a Tcl 8 module, it will operate as follows :%module example // Typemap for converting Tcl8 integers to C integers // $source is the Tcl input object // $target is the C variable (an int). %typemap(tcl8,in) int { int error = Tcl_GetDoubleFromObj(interp, $source, &$target); if (error != TCL_OK) return error; printf("Received : %d\n", $target); } ... extern int fact(int n);
% fact 6 Received : 6 720 %
In this case, the C code used for converting integers from Tcl to C has been changed.
Typemaps can also be applied to named datatypes such as function arguments. For example :
In this case, the typemap is only applied to function arguments that exactly match "double positive". Thus, from Tcl, these functions would operate as follows :%module example %include exception.i // Typemap for checking the value of an input argument // (this is done after it has been converted from Tcl to C) %typemap(tcl8,check) double positive { if ($source <= 0) { SWIG_exception(SWIG_ValueError, "Expected a positive value"); } } double log10(double positive); double exp(double);
% log10 2 0.301029996554 % exp -1 0.367879441171 % log -1 Expected a positive value! %
Typemaps are applied using a simple name-based pattern matching rule. When a typemap for `int' is given, it will be applied to all future occurrences of `int'. When a typemap for `double positive' is given, it will only be applied to arguments that match `double positive' (it would not be applied to other uses of `double'). However, it is possible to apply existing typemap rules to other datatypes using the %apply directive. For example :
At first glance, the specification of a typemap may look like a horrid mess (which is probably true), but there is a method behind the madness. Typemaps have four essential pieces that are specified as follows :%module example %include exception.i // Typemap for checking the value of an input argument // (this is done after it has been converted from Tcl to C) %typemap(tcl8,check) double positive { if ($source <= 0) { SWIG_exception(SWIG_ValueError,"Expected a positive value"); } } // Now apply this typemap to some other datatypes %apply double positive { double px, Real positive }; double log10(double px); // Only accepts positive values typedef double Real; Real log(Real positive); // Only accepts positive values ...
lang explicitly specifies the target scripting language. Typemaps for any of the target scripting languages can appear at any time in SWIG's input. However, only those matching the current target language will ever be used. method specifies the name of a particular type conversion. There are many different types of conversions, but some of the more common ones are as follows%typemap(lang, method) type [name] [,type2 [name],type3 [name],...] { Conversion Code }
type and name specify the C/C++ datatype that the typemap conversion is being given for. Additional datatypes can also be given as a comma separated list. Finally, the C/C++ conversion code is given in braces. Within the conversion code, a number of special placeholders can be included (this is only a partial list) :in - Convert function arguments from Tcl to C. out - Convert function result from C to Tcl. argout - Convert a result returned in an argument from C to Tcl. default - Default argument value check - Checks the value of an input argument. ret - Clean up the return result of a function ignore - Tell SWIG to ignore a function argument.
$source - The original value (before conversion) $target - The result of a data conversion $type - The C datatype used in the typemap
The placeholders get filled in by appropriate values when SWIG generates wrapper code.Thus, a more complicated typemap specification might look like this :
// Convert a variety of integer types from Tcl to C %typemap(tcl8,in) int, short, long, unsigned, unsigned short, unsigned long { int temp; if (Tcl_GetIntFromObj(interp, $source, &temp) == TCL_ERROR) { return TCL_ERROR; $target = ($type) temp; }
At this point, assuming that you're not completely confused, you may be wondering how the typemap system is really intended to work. Although many of the details about typemaps have been omitted here, the underlying idea behind typemaps is that you can specify special processing rules in advance and use them in an interface by simply following a naming convention. For example, if you wanted to wrap the C math library with some added error checking, it might look like this :
When working with mixed SWIG/C header files, this arrangement allows a file to be acceptable to both SWIG and the C compiler (provided that the SWIG directives are conditionally compiled of course). It also encourages header files and interface specifications to use a consistent naming scheme for different types of function arguments.%module math %{ #include <math.h> %} typemap(tcl8,check) double positive { ... } typemap(tcl8,check) double nonnegative { ... } typemap(tcl8,check) double nonzero { ... } ... // Now list ANSI C declarations double sin(double); double cos(double); double log(double positive); double log10(double positive); double sqrt(double nonnegative);
In addition to preparing an input file, you may need to eliminate the main() function from the original C/C++ application. Scripting languages provide their own main() that will be used instead. To eliminate main(), you can either omit it when you compile the C code or simply redefine the symbol such as
Finally, SWIG is not a full C/C++ compiler and may not support certain features. SWIG will report compilation problems in these cases. To eliminate the problems, you may have to tweak the SWIG input file somewhat. For example :% cc -c -Dmain=mymain main.c
#ifndef SWIG int printf(char *fmt, ...); // varags not supported by SWIG #else
An alternative approach to writing helper functions is to explicitly specify C++ constructors and destructors in the interface file. For example :%module example %{ #include "vector.h" %} // Now define some helper functions for creating/destroying vectors %inline %{ Vector *new_Vector(double x, double y, double z) { Vector *v = (Vector *) malloc(sizeof(Vector)); v->x = x; v->y = y; v->z = z; return v; } void delete_Vector(Vector *v) { free(v); } %}
This approach works from both C and C++. In the case of ANSI C, SWIG will simply create constructor and destructor functions that are mapped onto malloc() and free().%module %{ #include "vector.h" %} struct Vector { Vector(); ~Vector(); double x,y,z; };
From Tcl, these functions would be called explicitly :// SWIG helper functions for arrays %inline %{ /* Create an array */ double *double_array(int size) { return (double *) malloc(size*sizeof(double)); } /* Get a value from an array */ double double_get(double *a, int index) { return a[index]; } /* Set a value in the array */ double double_set(double *a, int index, double value) { return (a[index] = value); } %} %name(double_destroy) void free(void *);
In this case, the array is simply managed as a pointer that can be passed around to any C function that accepts a double *. Generally speaking, this is a good approach if you are working with very large C arrays since they can be created and passed around without any data copying.% set d [array_double 100] ;# Create 100 doubles % puts $d _10045f8_double_p % for {set i 0} {$i < 100} {incr i} { double_set $d $i $i ;# Set a value } % puts [double_get $d 50] ;# Get a value 50.0 % double_destroy $d ;# Destroy the array
SWIG provides a library file array.i that contains helper functions for int, float, double, and char * datatypes. To use this library, simply place the `%include array.i' directive into your interface file.
A common question that arises with arrays is "can I convert a Tcl list into a C array?" Although the pointer model is adequate for many situations, it may make more sense to work with Tcl lists in certain cases. There are a couple of ways to handle this. First, you can write functions in Tcl to convert a list to a pointer. For example :
Within a Tcl script, you would now do the following :# Tcl functions for converting a Tcl list to a C array proc DoubleListToArray {l} { set length [llength $l] set a [double_array $length] set i 0 foreach item $l { double_set $a $i $item incr i 1 } return a }
Alternatively, it is possible to use typemaps as a conversion mechanism. For example :set a [DoubleListToArray {2.3 4.5 0.5 -13.0}] ;# Create an array foo $a ;# Call a C function double_destroy $a
Although this might take a little time to digest (considering our limited discussion of typemaps), when given a C function such as the following,%typemap(tcl,in) double [ANY] (double temp[$dim0]) { char **items; int itemc,i; if (Tcl_SplitList(interp,$source,&itemc,&items) == TCL_ERROR) { return TCL_ERROR; } if (itemc != $dim0) { interp->result = "Array size mismatch!"; free(items); return TCL_ERROR; } for (i = 0; i < itemc; i++) { temp[i] = atof(items[i]); } $target = temp; free(items); }
the typemap makes it possible for the function to be called from Tcl as follows :void foo (double a[4]);
If the list passed to the function does not match the C array size, an error will be generated. Needless to say, there are a variety of ways to manage arrays depending on the problem at hand and your personal preference.foo { 2.3 4.5 0.5 -1.30 }
The output argument makes this a little tricky to handle from Tcl, but there are several techniques. That can be used. One approach is to write functions that manufacture a `double' and pass it to the C function. For example :void add(double a, double b, double *result) { *c = a+b; }
Now, from Tcl, you would do this :%inline %{ double *create_double(double val) { double *d = (double *) malloc(sizeof(double)); *d = val; return d; } double get_double(double *d) { return *d; } double set_double(double *d, double val) { return (*d = val); } %}
% set result [create_double 0.0] % add 4.5 9 $result ; Call our C function % puts [get_double $result] 13.5 %
If working with C built-in datatypes, it may be easier to use the SWIG pointer library by including the file `pointer.i' in your interface. The pointer library adds several new functions for manipulating pointers to various built-in datatypes and can be used as follows :
Finally, simple output arguments can be handled through the use of typemaps. This can be done by including the file `typemaps.i' in your interface. For example :% set result [ptrcreate double] ; # Create a double % add 4.5 9 $result % puts [ptrvalue $result] ; # Dereference the result 13.5 % ptrfree $result
Now, the function operates as follows :%module example %include typemaps.i // Apply an output rule to the output argument %apply double *OUTPUT { double *result }; // Now declare the function void add(double a, double b, double *result);
% set result [add 4.5 9] % puts $result 13.5 %
SWIG is unable to create Tcl wrappers for macros because there is no type information available. However, you can create a wrapper by simply giving SWIG a function prototype.#define Image_width(im) (im->xpixels)
The prototype gives SWIG the type information it needs to generate a wrapper. Otherwise, the fact that the "function" is really a macro doesn't matter.%module example %{ #include "header.h" %} // A wrapper for a C macro unsigned Image_width(Image *im);
class foo { public: foo(); %name(copy_foo) foo(foo &f); ... };
Overloaded operators can be handled by writing helper functions. For example :
%inline %{ Vector vector_add(Vector &a, Vector &b) { return a+b; } %}
However, the use of operators from Tcl may result in memory leaks and other problems without some special care. In the above case, SWIG would allocate memory to store the result and return a pointer to Tcl. It would be up to you to explicitly free this memory when you were done with it.
set r [vector_add $v1 $v2] ;# Creates a new vector ... use it ... delete_Vector $r ;# Free the result
If you have already written some Tcl wrapper functions, these can be added to a SWIG interface using the %native directive. For example :// Initialize BLT when loaded %init %{ if (Blt_Init(interp) == TCL_ERROR) { return TCL_ERROR; } %}
or simply%native(foo) int foo_wrap(Tcl_Interp *, ClientData, int, char **);
These declarations create a new Tcl command `foo' that is mapped onto the Tcl wrapper function foo_wrap.%native(foo) foo_wrap;
Similar techniques can be used to wrap C++ templates and work with other complicated declarations. Of course, it's also possible to confuse yourself.// Array access helper functions %module array // Define a macro for creating helper functions %define ARRAY(Type,Name) %inline %{ /* Create an array */ Type * Name ## _array (int size) { return (Type *) malloc(sizeof( Type )); } /* Get an item */ Type Name ## _get (Type *a, int index) { return a[index]; } /* Set an item */ Type Name ## _set (Type *a, int index, Type value) { return (a[index] = value); } /* Delete the array */ void Name ## _destroy (Type *a) { free(a); } %} %enddef // Now create a bunch of helper functions ARRAY(double,double) ARRAY(float,float) ARRAY(int,int) ARRAY(short,short) ARRAY(unsigned int, unsigned) ARRAY(long, long) ARRAY(struct node, node)
A variety of plotting primitives such as lines, circles, boxes, and fonts are also available to make more complicated images.#include <gd.h> #include <stdio.h> int main() { gdImagePtr im; FILE *out; int black, white; /* Create an image */ im = gdImageCreate(200,200); /* Allocate some colors */ black = gdImageColorAllocate(im,0,0,0); white = gdImageColorAllocate(im,255,255,255); /* Draw a line */ gdImageLine(im,20,50,180,140,white); /* Output the image */ out = fopen("test.gif","wb"); gdImageGif(im,out); fclose(out); /* Clean up */ gdImageDestroy(im); }
%module gd %{ #include <gd.h> %} // Just grab the gd.h header file %include gd.h // Plus a few file I/O functions (to be explained shortly) FILE *fopen(char *filename, char *mode); void fclose(FILE *f);
Now, you can compile a module (shown for Linux)
% swig -tcl gd.i Making wrappers for Tcl gd.h : Line 31. Warning. Array member will be read-only. gd.h : Line 32. Warning. Array member will be read-only. gd.h : Line 33. Warning. Array member will be read-only. gd.h : Line 34. Warning. Array member will be read-only. gd.h : Line 40. Warning. Array member will be read-only. gd.h : Line 41. Warning. Array member will be read-only. % gcc -c -fpic gd_wrap.c % gcc -shared gd_wrap.o -o gd.so -lgd
The warning messages will be explained shortly and can be ignored. Now, you can write a simple Tcl script
In a manner of only a few minutes, we have built a Tcl interface that seems to work.load ./gd.so gd # Create an image set im [gdImageCreate 200 200] # Allocate colors set black [gdImageColorAllocate $im 0 0 0] set white [gdImageColorAllocate $im 255 255 255] # Draw a line gdImageLine $im 20 50 180 140 $white # Write the image set out [fopen test.gif wb] gdImageGif $im $out fclose $out # Clean up gdImageDestroy $im
typedef struct gdImageStruct { unsigned char ** pixels; int sx; int sy; int colorsTotal; int red[gdMaxColors]; ! Warning int green[gdMaxColors]; ! Warning int blue[gdMaxColors]; ! Warning int open[gdMaxColors]; ! Warning int transparent; int *polyInts; int polyAllocated; struct gdImageStruct *brush; struct gdImageStruct *tile; int brushColorMap[gdMaxColors]; ! Warning int tileColorMap[gdMaxColors]; ! Warning int styleLength; int stylePos; int *style; int interlace; } gdImage;
The message "Array member will be read-only" means that SWIG knows how to return the value of an array data member (the value is a pointer to the first element), but does not know how to set the value of the array. From Tcl, you will see the following behavior
Although it is not possible to modify array structure members as shown, you could do so using the SWIG pointer library or helper functions. For example,% set im [gdImageCreate 200 200] _80b51e8_gdImagePtr % gdImage_red_get $im ; # Get an array member _80b51f8_int_p % gdImage_red_set $im 0 ; # Try to change a read-only member invalid command name "gdImage_red_set" %
Now, in Tcl% swig -tcl -lpointer.i gd.i
Of course, you could also end up shooting yourself in the foot by manipulating things in bizarre ways (SWIG certainly won't stop you from doing this).% set im [gdImageCreate 200 200] % gdImageColorAllocate $im 50 200 100 % gdImageColorAllocate $im 35 210 75 % set r [gdImage_red_get $im] _80b51f8_int_p % ptrvalue $r 0 ;# Get r[0] 50 % ptrvalue $r 1 ;# Get r[1] 35 % ptrset $r 70 1 ;# change r[1] % ptrvalue $r 1 70 %
gdImagePtr gdImageCreateFromGif(FILE *fd); gdImagePtr gdImageCreateFromGd(FILE *in); gdImagePtr gdImageCreateFromXbm(FILE *fd); void gdImageGif(gdImagePtr im, FILE *out); void gdImageGd(gdImagePtr im, FILE *out);
An ideal solution would be to use Tcl file handles as FILE * arguments. Unfortunately, this requires you to unravel file handles and convert them into the corresponding FILE * structure--a task that is not easily accomplished. For the purposes of quickly making an interface, it is often easier to simply include a few supporting functions such as fopen() and fclose(). These functions can be called directly to create files for use with the library. Although files created with these functions are not the same as Tcl file handles, they are still easy to use.
#define gdImageSX(im) ((im)->sx) #define gdImageSY(im) ((im)->sy) #define gdImageColorsTotal(im) ((im)->colorsTotal) #define gdImageRed(im, c) ((im)->red[(c)]) #define gdImageGreen(im, c) ((im)->green[(c)]) #define gdImageBlue(im, c) ((im)->blue[(c)]) #define gdImageGetTransparent(im) ((im)->transparent) #define gdImageGetInterlaced(im) ((im)->interlace)
SWIG won't wrap these macros directly, but you can write some function prototypes to accomplish the same thing.
Within Tcl, these macros can now be used just like ordinary functions%module gd %{ #include <gd.h> %} // Just grab the gd.h header file %include gd.h // Clear the previous macro definitions included in gd.h #undef gdImageSX #undef gdImageSY #undef gdImageColorsTotal #undef gdImageRed #undef gdImageGreen #undef gdBlue #undef gdImageGetTransparent #undef gdImageGetInterlaced // Provide SWIG with function prototypes for the macros int gdImageSX(gdImagePtr im); int gdImageSY(gdImagePtr im); int gdImageColorsTotal(gdImagePtr im); int gdImageRed(gdImagePtr im, int c); int gdImageGreen(gdImagePtr im, int c); int gdImageBlue(gdImagePtr im, int c); int gdImageGetTransparent(gdImagePtr im); int gdImageGetInterlaced(gdImagePtr im); // Plus a few file I/O functions FILE *fopen(char *filename, char *mode); void fclose(FILE *f);
% set im [gdImageCreate 200 200] % gdImageColorAllocate $im 233 50 40 % gdImageSX $im 200 % gdImageColorsTotal $im 1 %
void gdImageChar(gdImagePtr im, gdFontPtr f, int x, int y, int c, int color);
where gdFontPtr is a pointer to a font structure. Each font is defined by a global variable that is defined in a separate header file such as the following :
#ifndef GDFONTS_H #define GDFONTS_H 1 /* gdfonts.h: brings in the smaller of the two provided fonts. Also link with gdfonts.c. */ #include "gd.h" /* 6x12 font derived from a public domain font in the X distribution. Only contains the 96 standard ascii characters, sorry. Feel free to improve on this. */ extern gdFontPtr gdFontSmall; #endif
To make the built-in fonts available to our Tcl interface, you need to make the font pointers available. Since the font header files are fairly simple, this can be done by simply including the header files in our SWIG interface such as follows :
// Make the gd fonts available as read-only variables %readonly %include "gdfontt.h" %include "gdfonts.h" %include "gdfontmb.h" %include "gdfontl.h" %include "gdfontg.h" %readwrite
The fact that the fonts are global variables presents us with a slight complication. SWIG normally tries to use the Tcl variable linking mechanism, but this only supports the basic datatypes of double, int, and char *. To access other types (including pointers), SWIG generates accessor functions. Thus, in Tcl, these variables are accessed as follows :
Since this can be a little awkward, it may make more sense to write a simple Tcl script to fix the problem% gdFontTiny_get _400f695c_gdFontPtr % gdImageChar $im [gdFontTiny_get] 20 20 A $white
Now, you would just write the following :# gdfonts.tcl : Extract the fonts into Tcl variables set gdFontTiny [gdFontTiny_get] set gdFontSmall [gdFontSmall_get] set gdFontMediumBold [gdFontMediumBold_get] set gdFontLarge [gdFontLarge_get] set gdFontGiant [gdFontGiant_get]
If writing a little Tcl script to work around the problem isn't your preference, it's also possible to create the variables within the SWIG module. For example :# Simple gd script load ./gd.so source gdfonts.tcl set im [gdImageCreate 200 200] set black [gdImageColorAllocate $im 0 0 0] set white [gdImageColorAllocate $im 255 255 255] # Draw some text gdImageString $im $gdFontLarge 10 50 "Hello World" $white # Write the image set out [fopen test.gif wb] gdImageGif $im $out fclose $out # Clean up gdImageDestroy $im
SWIG_MakePtr() is a function SWIG uses to create pointer variables. Since this approach would only work with Tcl, conditional compilation is used to make sure this code isn't included when producing interfaces for other languages (SWIGTCL is a symbol defined by SWIG when it is running in Tcl mode).// Create some Tcl variables for the fontsstartup #ifdef SWIGTCL %init %{ { char temp[64]; SWIG_MakePtr(temp, gdFontTiny, "_gdFontPtr"); Tcl_SetVar(interp,"gdFontTiny",temp,0); SWIG_MakePtr(temp, gdFontSmall, "_gdFontPtr"); Tcl_SetVar(interp,"gdFontSmall",temp,0); SWIG_MakePtr(temp, gdFontMediumBold, "_gdFontPtr"); Tcl_SetVar(interp,"gdFontMediumBold",temp,0); SWIG_MakePtr(temp, gdFontLarge, "_gdFontPtr"); Tcl_SetVar(interp,"gdFontLarge",temp,0); SWIG_MakePtr(temp, gdFontGiant, "_gdFontPtr"); Tcl_SetVar(interp,"gdFontGiant",temp,0); } %} #endif
These functions require an array of points to be passed as the second argument. Points are defined by the following structure :void gdImagePolygon(gdImagePtr im, gdPointPtr p, int n, int c); void gdImageFilledPolygon(gdImagePtr im, gdPointPtr p, int n, int c);
As is, our Tcl interface has no mechanism for creating arrays of points so we will need to provide some helper functions for this. Here's one way to do ittypedef struct { int x, y; } gdPoint, *gdPointPtr;
From Tcl, points can now be created and manipulated as follows :// Helper functions for points %inline %{ gdPoint *new_points(int npoints) { return (gdPoint *) malloc(npoints*sizeof(gdPoint)); } void delete_points(gdPoint *p) { free(p); } void set_point(gdPoint *p, int n, int x, int y) { p[n].x = x; p[n].y = y; } %}
# Create an array of points set p [new_points 4] set_point $p 0 10 20 set_point $p 1 150 30 set_point $p 2 95 150 set_point $p 3 20 70 # Draw a polygon gdImagePolygon $im $p 4 $white # Delete the points delete_points $p
Now an interactive gd session# watchgif.tcl : Periodically poll a file and display in the canvas set atime "" set canvasinit 0 set current_img "" proc watchgif {fname} { global atime filename set filename $fname set newatime "" catch {set newatime [file mtime $filename]} if { $newatime != $atime } { set atime $newatime update_image } after 1000 "watchgif $filename" } proc update_image {} { global canvasinit canvas current_img filename set img [image create photo -file $filename] if {$canvasinit == 0 } { canvas .img pack .img set canvasinit 1 } else { image delete $current_img } .img delete imagedata set width [image width $img] set height [image height $img] .img configure -width $width -height $height .img create image 0 0 -image $img -anchor nw -tag imagedata set current_img $img } proc write {im} { global filename set out [fopen $filename wb] gdImageGif $im $out fclose $out }
Is this example, various gd library functions are issued interactively. Whenever you want to see the current image, simply type `write $im' and the image currently displayed in the canvas will be updated. The interface is minimal, but you can use it to play with the library, see how it works, and even write little Tcl scripts to try things out.> wish % load ./gd.so % source gdfonts.tcl % source watchgif.tcl % watchgif test.gif ;# Watch a file for changes and display % set im [gdImageCreate 300 300] _817d108_gdImagePtr % set black [gdImageColorAllocate $im 0 0 0] 0 % set white [gdImageColorAllocate $im 255 255 255] 1 % gdImageLine $im 20 20 250 70 $white % gdImageString $im $gdFontLarge 20 70 "Hello World" $white % write $im % gdImageArc $im 100 100 50 20 0 270 $white % write $im
# gdtest.tcl : Generic gd test script # # This scripts sets up some default values, creates an image # and executes a test script. # # usage : tclsh gdtest.tcl script.tcl load ./gd.so source gdfonts.tcl # Configuration options set width 400 set height 400 # Create an image set im [gdImageCreate $width $height] # Create some colors set black [gdImageColorAllocate $im 0 0 0] set white [gdImageColorAllocate $im 255 255 255] set red [gdImageColorAllocate $im 255 0 0 ] set green [gdImageColorAllocate $im 0 255 0 ] set blue [gdImageColorAllocate $im 0 0 255] set yellow [gdImageColorAllocate $im 255 255 0] set cyan [gdImageColorAllocate $im 0 255 255] set magenta [gdImageColorAllocate $im 255 0 255] # Get the filename of the test script set filename [lindex $argv 0] # Run the test script source $filename # Output the image append filename ".gif" set out [fopen $filename wb] gdImageGif $im $out fclose $out puts "Image written to $filename"
This script simply sets up an image and some default values. To use it, you would write a short test script such as
# lines.tcl : Draw some lines gdImageLine $im 10 10 300 50 $white gdImageLine $im 50 200 290 10 $red ...
and run it as follows :
Finally, if you wanted to develop an entire testing suite, you can write a collection of scripts to exercise various features along with a simple testing program.% tclsh gdtest.tcl lines.tcl Image written to lines.tcl.gif %
In the test program, each test is run as a separate process using `exec'. The reason for this is that if a test fails with an internal error (such as a segmentation fault), it won't affect any of the other tests (in other words, testing can proceed even if individual parts fail). When running the test, you will now get output such as the following :# gd testing script set files { lines.tcl arcs.tcl fonts.tcl fill.tcl polygons.tcl ... } foreach f $files { puts -nonewline "Testing $f ... " if [catch {set msg [exec tclsh gdtest.tcl $f ]}] { set msg "Failed!" } puts $msg }
While simple, you can imagine writing a large collection of test scripts to exercise various features of a library. By writing the test scripts in Tcl, they are easy to write and easy to modify. In fact, a powerful testing suite can be developed without having to write additional programs in C/C++.% tclsh test.tcl Testing lines.tcl ... Image written to lines.tcl.gif Testing arcs.tcl ... Image written to arc.tcl.gif Testing fonts.tcl ... Image written to fonts.tcl.gif Testing fill.tcl ... Failed! Testing polygons.tcl ... Image written to polygons.tcl.gif %
Now, images can be manipulated almost like a Tk widget%module gd %{ #include "gd.h" %} %include gd.h ... %addmethods gdImage { gdImage(int w, int h) { return gdImageCreate(w,h); } ~gdImage() { gdImageDestroy(self); } int color(int r, int g, int b) { return gdImageColorAllocate(self,r,g,b); } void plot(int x, int y, int c) { gdImageSetPixel(self, x, y, c); } void line(int x1, int y1, int x2, int y2, int c) { gdImageLine(self,x1,y1,x2,y2,c); } void poly(gdPointPtr pts, int npoints, int c) { gdImagePolygon(self,pts, npoints, c); } void rect(int x1, int y1, int x2, int y2, int c) { gdImageRectangle(self,x1,y1,x2,y2,c); } void string(gdFontPtr font, int x, int y, char *str, int c) { gdImageString(self,font,x,y,str,c); } void write(char *filename) { FILE *f = fopen(filename,"wb"); if (f == NULL) return; gdImageGif(self,f); fclose(f); } }
load ./gd.so gdImage img 400 400 ;# Create an image set black [img color 0 0 0] ;# Allocate some colors set white [img color 255 255 255] img line 20 20 300 300 $white ;# Draw some objects img rect 40 40 250 250 $white img string $gdFontLarge 10 100 "Hello World" $white img write "test.gif" ;# Output the image rename img "" ;# Destroy the image
As a result, it is easy to write scripts in other languages. For example :swig -perl5 gd.i # Create Perl5 module swig -python gd.i # Create a Python module
or# A Perl5 Script use gd; # Create an image $im = gd::gdImageCreate(200,200); # Allocate colors $black = gd::gdImageColorAllocate($im,0,0,0); $white = gd::gdImageColorAllocate($im,255,255,255); # Draw a line gd::gdImageLine($im,20,50,180,140,$white); # Write the image $out = gd::fopen("test.gif",wb); gd::gdImageGif($im,$out); gd::fclose($out); # Clean up gd::gdImageDestroy($im);
Although you might not consider the use of other scripting languages to be so important, consider the process of upgrading to newer version of Tcl. Tcl 7 was based entirely on strings (the "everything is a string" model) while Tcl 8 uses dual-ported objects. Using objects provides better performance and upgrading a SWIG module from Tcl 7 to Tcl 8 is usually trivial -- just rebuild all of your interfaces by running SWIG with the -tcl8 option.# A Python script import gd im = gd.gdImageCreate(200,200) # Allocate colors black = gd.gdImageColorAllocate(im,0,0,0) white = gd.gdImageColorAllocate(im,255,255,255) # Draw a line gd.gdImageLine(im,20,50,180,140,white) # Write the image out = gd.fopen("test.gif",wb) gd.gdImageGif(im,out) gd.fclose(out) # Clean up gd.gdImageDestroy(im)
This chapter has only scratched the surface of SWIG and its use with Tcl. In addition to its use in application development, SWIG can also be used to build general purpose Tcl extension modules and as a tool for building graphical user interfaces to C/C++ applications (using Tk). Readers interested in other SWIG applications should take a look at the web-page (www.swig.org) and the SWIG Users Manual.
SWIG would not be possible without the valuable contributions of its users. Although there are far too many people to thank individually at this point, you know who you are. SWIG would certainly not be in its current state without their feedback.