Tcl and SWIG as a C/C++ Development Tool

David M. Beazley
Department of Computer Science
University of Chicago
Chicago, Illinois 60637
beazley@cs.uchicago.edu

Copyright (C) 1998
All Rights Reserved

About This Document

This paper was originally written as a chapter for a Tcl book project. However, that project ran into some difficulties so the chapter (and the book itself) never appeared. Since the chapter may be of use to SWIG and Tcl users, it has been slightly revamped and republished in electronic form here. Permission is hereby granted to distribute this document in electronic form without royalties or fees provided that the above copyright and this notice appear in all reproductions. Hard-copy (paper) reproductions of this article are strictly prohibited without the written consent of the author.

Introduction

One of the greatest strengths of Tcl is its ability to be easily extended with code written in C or C++. More often than not, compiled extensions to Tcl take the form of Tk widgets and general purpose libraries that provide access to databases, sockets, and other system services. Application developers might also extend Tcl in order to build a user-interface to a large package written in a compiled language. However, many C/C++ developers might be surprised to learn that Tcl can also be used as a general purpose C/C++ development tool--even in situations where Tcl is not part of the final product or design.

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.

Tcl Extension Building

Tcl provides an extensive C API that can be used to create compiled extensions in C or C++. To integrate Tcl and C/C++, it is necessary to write "wrapper functions" that provide the glue between C and the Tcl interpreter. To illustrate, suppose that you wanted to access the pow(x,y) function in the C math library from Tcl. To do this, you would write a wrapper function such as the following :

/* 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;
}

To make this new command available to Tcl, you also need to write an initialization function such as follows:

int 
Example_Init(Tcl_Interp *interp) {
		Tcl_CreateObjCommand(interp, "pow", PowObjCmd,
		        (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
		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 :

tclsh
% load ./example.so Example
% pow 2 3
8.0
%

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.

/* 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;
}

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

A 30 Second Introduction to SWIG

SWIG is a development tool that automates the Tcl extension building process. For example, consider the earlier example of the pow(x,y) function. With SWIG, the Tcl extension can be defined entirely by the following file (by convention these are called "interface files" and are usually given a .i suffix) :

// example.i : A Simple SWIG interface
%module example
%{
#include <math.h>
%}

// ANSI C/C++ prototypes
double pow(double x, double y);

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

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) :

> 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
%

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.

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!).

A Tour of SWIG

In this section, I provide a high-level tour of SWIG. This is intended to be a quick overview and not a replacement for the SWIG users manual (which contains all of the gory details).

The Design and Implementation of SWIG

In a nutshell, SWIG is a special purpose C/C++ compiler. As input, SWIG accepts ANSI C/C++ definitions and translates them into the C wrapper code needed to access that functionality from a variety of scripting languages. SWIG also understands a number of directives that are preceded by a `%'. These directives augment the ANSI C definitions and provide additional information that SWIG uses when generating the wrapper code. Finally, in addition to producing wrapper code, SWIG also generates documentation files (in ASCII, HTML, or LaTeX).

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.

Running SWIG

SWIG is invoked using the swig command. SWIG operates like a C compiler and accepts a number of options :

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.

The Input to SWIG

Most interface files have the following format

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

The Basic Tcl Interface

SWIG attempts to provide a direct mapping between C and Tcl. C functions are turned into new Tcl commands. Global variables are turned into Tcl variables using the variable linking mechanism if possible. Constants are simply turned into read-only Tcl variables. Thus, if you give SWIG the following interface file :

%module
example

int factorial(int n);
extern double My_variable;
#define PI 3.1415926
const int SPAM = 42;
...

These declarations are accessible in Tcl as follows

% 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
%

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,

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

SWIG supports C/C++ pointers by mapping them into character strings that encode the value and type. For example, a pointer of type "Vector *" would look similar to the following in Tcl :

_100f8e2_Vector_p

Pointers may appear throughout a SWIG interface file. Furthermore, type definitions are not generally required. For example :

%module vector
%{
#include "vector.h"
%}

Vector *new_vector(double x, double y, double z);
double dot_product(Vector *a, Vector *b);
...

In Tcl, pointers are opaque objects that can be passed around between different C functions and operate like you would expect. For example :

% 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
%

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

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

Structures and Classes

The SWIG pointer mechanism is a powerful way to manipulate C objects from Tcl. However, you may also want to access the internals of an object. To support this, SWIG can parse C structure definitions and turn them into "accessor" functions. For example,

struct Vector {
      double x,y,z;
};

is translated into the following collection of C functions :

double 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);
}

Accessor functions simply take an object as the first argument and provide a way to manipulate its internal representation from Tcl. For example

# v is a Vector that got created somehow
% Vector_x_get $v
3.5
% Vector_x_set $v 7.8            ;# Change x component

Similar access is provided for unions and the data members of C++ classes.

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 :

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);
}

Within Tcl, you can use the functions as follows :

% 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 :

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();
};

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 :

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.

The Object Interface

Although SWIG turns structures and classes into accessor functions, an optional object-oriented interface is also available. This interface allows C/C++ objects to appear like Tcl objects (or Tk widgets). As an example, consider the List class mentioned in the previous section. Using the optional object interface, you could write the following Tcl code

% 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

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:

set t [l cget -this]      # Extract the pointer value

In other cases, it may be useful to turn an existing pointer into a Tcl object. This can be done as follows :

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.

Renaming

Sometimes the contents of a C/C++ library will generate a namespace clash with existing Tcl keywords and commands. To resolve these conflicts, SWIG provides the %name directive. For example :

%module example
...
%name(mylindex) int lindex();
...
%name(clist) class list {
public:
    ...
};

%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).

Read-only variables

To restrict access to global variables and data members of structures, SWIG provides the %readonly and %readwrite directives. For example :

%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
...
};

Read-only mode stays in effect until it is explicitly disabled with the %readwrite directive.

File Inclusion and the SWIG Library

SWIG provides an %include directive that can be used to break up interfaces into multiple files. For example, if you were creating an interface to a large package, you might write an interface like this :

// Interface to OpenGL
%module opengl

// Include a variety of pieces
%include gl.i
%include glu.i
%include "GL/gluaux.h"
%include "test.c"

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 :

%module example
%{
#include "myheader.h"
%}
%include tclsh.i              // Include Tcl_AppInit() code for tclsh

// C declarations

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.

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 Insertion

Sometimes it is useful to insert support code into the resulting wrapper file generated by SWIG. The wrapper file produced by SWIG consists of three sections---a header section containing declarations that must appear before any wrapper functions, a wrapper code section containing the actual Tcl wrapper functions, and an initialization function that registers the module's functionality with Tcl.

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 :

// 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();

Since writing helper functions is relatively common, a shorthand form of the above is available using the %inline directive as follows :

%inline %{
/* Create a new vector */
Vector *new_Vector() {
    return (Vector *) malloc(sizeof(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.

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.

Structure and Class Extension

The object-oriented interface provides a nice way to manipulate C structures and C++ classes. However, in some cases, you may want to extend structures and classes. For example, suppose you wanted to manipulate the following C structure from Tcl :

struct Vector {
     double x, y, z;
};

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 :

%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);
       }
};

Now, in Tcl, you can do the following :

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

Exception Handling

In some C/C++ applications it may be useful to catch errors and translate them into Tcl errors. This can be done using the %except directive. 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;
    }
}

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 :

 %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 :

// 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");
     }
}

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.

Typemaps

Most of SWIG's internal processing is driven by the handling of C datatypes--in particular, rules for converting C datatypes to and from Tcl (and other scripting languages). For example, when SWIG encounters an "int", it needs to know how to convert an integer from Tcl to C, return an integer result to Tcl, link to integer variables, and so forth. By default, SWIG already knows how to perform these simple conversions as well as mapping complex objects into pointers.

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 :

%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);

When this example is compiled into a Tcl 8 module, it will operate as follows :

% 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 :

%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);

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 :

% 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 :

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

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 :

%typemap(lang, method) type [name] [,type2 [name],type3 [name],...] {
       Conversion Code
}
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

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.

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) :

$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 :

%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);

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.

Extensive information about typemaps is provided in the SWIG Users Manual. For the purposes of this chapter, you should know that typemaps exist and that they are often used behind the scenes to perform many magical tasks. As it turns out, a number of useful typemaps are included in the SWIG library---sparing you the trouble of writing them by hand (but, more about that in a little bit).

Using SWIG

Now that you've had a quick tour of the SWIG compiler, it's time to start using it. This section describes some of the techniques (and tricks) that are commonly used to build Tcl interfaces to C/C++ code. Rather than attempting to cover every possible situation, this section covers many of the most common problems and solutions. However, given the flexibility that SWIG provides, there is usually more than one way to do it (just something to keep in mind)

Preparing to SWIG

Using SWIG generally involves the following steps:

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

% cc -c -Dmain=mymain main.c

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 :

#ifndef SWIG
int printf(char *fmt, ...);       // varags not supported by SWIG
#else

Interface Building Considerations

Many users are surprised to find out how easy it is to use SWIG. However, this doesn't mean that you will get a good Tcl interface on the first try. In fact, there are a few things to keep in mind

Creating and Destroying Simple Objects

Sometimes it is necessary to manufacture various C/C++ objects from a Tcl interface. In C, objects are easily constructed using malloc() or new, but Tcl needs a little extra help. Thus, object creation and destruction often requires the use of helper functions. 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);
}
%}

An alternative approach to writing helper functions is to explicitly specify C++ constructors and destructors in the interface file. For example :

%module
%{
#include "vector.h"
%}

struct Vector {
    Vector();
   ~Vector();
    double x,y,z;
};

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().

Arrays

Manipulating arrays is common practice in many C applications. C arrays can also be handled through the use of helper functions. For example :

// 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 *);

From Tcl, these functions would be called explicitly :

% 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

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.

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 :

# 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
}

Within a Tcl script, you would now do the following :

set a [DoubleListToArray {2.3 4.5 0.5 -13.0}]    ;# Create an array
foo $a                                           ;# Call a C function
double_destroy $a

Alternatively, it is possible to use typemaps as a conversion mechanism. For example :

%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);
}

Although this might take a little time to digest (considering our limited discussion of typemaps), when given a C function such as the following,

void foo (double a[4]);

the typemap makes it possible for the function to be called from Tcl as follows :

foo { 2.3 4.5 0.5 -1.30 }

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.

Output Arguments

Sometimes C functions return results in one of their arguments such as follows :

void add(double a, double b, double *result) {
      *c = a+b;
}

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 :

%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);
}
%}

Now, from Tcl, you would do this :

% 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 :

% set result [ptrcreate double]      ; # Create a double
% add 4.5 9 $result                  
% puts [ptrvalue $result]            ; # Dereference the result
13.5
% ptrfree $result

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 :

%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);

Now, the function operates as follows :

% set result [add 4.5 9]
% puts $result
13.5
%

Wrapping Preprocessor Macros

Many C programs use macros instead of function calls to perform some operation. For example :

#define Image_width(im)  (im->xpixels)

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.

%module example
%{
#include "header.h"
%}
// A wrapper for a C macro
unsigned Image_width(Image *im);

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.

Overloading

As of this writing, SWIG does not support C++ overloading of functions or operators. To resolve overloaded functions, the %name() directive can be used. For example :

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

Working with other Tcl Extensions

Although SWIG generates everything you need to build an interface, it can also be used with other Tcl extensions. If you would like to initialize another Tcl module when your module is loaded, you can write the following :

// Initialize BLT when loaded
%init %{
     if (Blt_Init(interp) == TCL_ERROR) {
          return TCL_ERROR;
     }
%}

If you have already written some Tcl wrapper functions, these can be added to a SWIG interface using the %native directive. For example :

%native(foo) int foo_wrap(Tcl_Interp *, ClientData, int, char **);

or simply

%native(foo) foo_wrap;

These declarations create a new Tcl command `foo' that is mapped onto the Tcl wrapper function foo_wrap.

SWIG Preprocessor Magic

The SWIG compiler includes a full C preprocessor that can be used in a variety of unusual ways. In fact, the preprocessor can be an extremely useful tool for quickly generating helper functions and other aspects of a Tcl interface. One of the more useful capabilities of the preprocessor is the %define directive that lets you define complicated macros. For example, the following interface file could be used to generate a variety of array helper functions

// 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)

Similar techniques can be used to wrap C++ templates and work with other complicated declarations. Of course, it's also possible to confuse yourself.

Putting it All Together

So far, you have been given a crash course in SWIG and some interface building techniques. In this section, we will build a Tcl interface to a C library, write some scripts, and show how you can use Tcl to interact with the library. In this example, we will use the gd-1.2 library written by Thomas Boutell and available at www.boutell.com. gd is relatively simple and can be used on both Unix and Windows platforms (making it a good example). Furthermore, there are already Tcl interfaces to gd, making it possible to compare to other approaches if you are so inclined.

A Brief Introduction to gd

gd is a C library that is used to create GIF images. A very simple example in C is as follows :

#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);
}

A variety of plotting primitives such as lines, circles, boxes, and fonts are also available to make more complicated images.

A Simple SWIG interface

To build a Tcl interface to gd, we can start with the following interface :

%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

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

In a manner of only a few minutes, we have built a Tcl interface that seems to work.

Understanding the Warning Messages

The warning messages generated by SWIG were generated from the following data structure :

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

% 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"
%

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,

% swig -tcl -lpointer.i gd.i

Now, in Tcl

% 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
%
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).

Working with Files

In our simple interface we included two functions to open and close files. A variety of functions in the gd library require the use of a FILE * argument such as the following :

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.

Wrapping Macros

The gd library provides a collection of macros for accessing various image attributes. Rather than manipulating the C data structure directly, users are advised to use these macros.

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

%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);

Within Tcl, these macros can now be used just like ordinary functions

% set im [gdImageCreate 200 200]
% gdImageColorAllocate $im 233 50 40
% gdImageSX $im
200
% gdImageColorsTotal $im
1
%

Wrapping the Fonts

gd is packaged with five different fonts that are used with functions such as

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 :

% gdFontTiny_get
_400f695c_gdFontPtr
% gdImageChar $im [gdFontTiny_get] 20 20 A $white

Since this can be a little awkward, it may make more sense to write a simple Tcl script to fix the problem

# 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]

Now, you would just write the following :

# 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

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 :

// 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

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

Creating Points

Finally, the gd library contains a few functions for drawing polygons such as

void gdImagePolygon(gdImagePtr im, gdPointPtr p, int n, int c);
void gdImageFilledPolygon(gdImagePtr im, gdPointPtr p, int n, int c);

These functions require an array of points to be passed as the second argument. Points are defined by the following structure :

typedef struct {
        int x, y;
} gdPoint, *gdPointPtr;

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 it

// 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;
   }
%}

From Tcl, points can now be created and manipulated as follows :

# 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

Using the Module

With a just a little work, the gd module is now fully functional and ready to be used. From Tcl, you can interactively play with library and test out its various functions. In fact you can even write a simple Tk script to watch the output file and display images in a canvas. For example :

# 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
}

Now an interactive gd session

> 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                   

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.

Writing Test Scripts

Where this approach really shines is the ability to write flexible test scripts. When developing C libraries, I always seem to find myself spending a huge amount of time writing test programs to exercise various parts of the library. Although I could write a collection of C programs to do this, using Tcl is often much more flexible. Suppose you wanted to test various aspects of the gd library. To do this, you might write a generic test script such as the following

# 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 :

% tclsh gdtest.tcl lines.tcl
Image written to lines.tcl.gif
% 

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.

# 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
}

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 :

% 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
% 

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

Building an Object Oriented Interface

One of the interesting parts of using SWIG is that there is almost always more than one way to do things. In the gd example, a straightforward Tcl interface has been built to C library. From Tcl, the library works in essentially the same manner as it does in C. However, it's also possible to package libraries in an entirely different manner. Using the SWIG class extension mechanism, the following code can be added to the SWIG interface:

%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);
    }
}

Now, images can be manipulated almost like a Tk widget

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

Porting to Other Languages

Although this chapter has focused exclusively on the use of SWIG and Tcl, nothing prevents us from using other scripting languages. In fact, looking at the example, no Tcl specific code is involved in creating the scripting interface (unless you went overboard with typemaps or Tcl specific helper functions). Because of this, it is trivial to retarget the interface to other environments by simply running SWIG with different command line options

swig -perl5 gd.i                 # Create Perl5 module
swig -python gd.i                # Create a Python module

As a result, it is easy to write scripts in other languages. For example :

# 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);

or

# 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)

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.

Summary

I hope to have given you a small taste of SWIG and how it can be used with C/C++ applications. From a personal perspective, I have found the combination of SWIG and Tcl to be a powerful development tool. First, SWIG makes it relatively easy to build Tcl interfaces to existing C code--in fact, I rarely have to dig out my Tcl books for this. Second, Tcl is a great way to interact with C libraries. Not only can I interactively tweak parameters, call functions, and try things, I can write testing scripts and short programs without having to write additional C code. Finally, this approach has had a noticeable impact on the quality of the software I have developed. Before having the Tcl interface, it was always a struggle to use the code I had developed. More often than not, I would spend inordinate amounts of time writing bad user interfaces and test programs. However, by having a Tcl interface, I was given an environment that lets me play with libraries, test components, and concentrate on the problem at hand. In many cases, this has allowed me to find design flaws and correct reliability problems much earlier than before.

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.


beazley@cs.uchicago.edu Fri Aug 14 10:25:19 1998