The following paper was originally published in the
Proceedings of the
USENIX
Fourth Annual
Tcl/Tk Workshop
Monterey, California, July 1996.
For more information about
USENIX Association
contact:
| 1. Phone: | (510) 528-8649 |
| 2. FAX: | (510) 548-5738 |
| 3. Email: | office@usenix.org |
| 4. WWW URL: | http://www.usenix.org |
int wrap_fact(ClientData clientData,
Tcl_Interp *interp,
int argc, char *argv[]) {
int result;
int arg0;
if (argc != 2) {
interp->result = "wrong # args";
return TCL_ERROR;
}
arg0 = atoi(argv[1]);
result = fact(arg0);
sprintf(interp->result,"%d",result);
return TCL_OK;
}
In addition to writing the wrapper function, a user will also need to
write code to add this function to the Tcl interpreter. In the case of
Tcl 7.5, this could be done by writing an initialization function to
be called when the extension is loaded dynamically. While writing
a wrapper function usually is not too difficult, the process quickly
becomes tedious and error prone as the number of functions increases.
Therefore, automated approaches for producing wrapper functions are
appealing--especially when working with a large number of C functions
or with C++ (in which case the wrapper code tends to get more complicated).
/* File : file.i */
%module fileio
%{
#include <stdio.h>
%}
FILE *fopen(char *filename, char *type);
int fclose(FILE *stream);
typedef unsigned int size_t
size_t fread(void *ptr, size_t size,
size_t nobj, FILE *stream);
size_t fwrite(void *ptr, size_t size,
size_t nobj,FILE *stream);
void *malloc(size_t nbytes);
void free(void *);
The %module directive
sets the name of the initialization function. This is optional, but is
recommended if building a Tcl 7.5 module.
Everything inside the %{, %}
block is copied directly into the output, allowing the inclusion of
header files and additional C code.
Afterwards, C/C++ function and variable declarations are listed in any
order.
Building a new Tcl module is usually as
easy as the following :
unix > swig -tcl file.i unix > gcc file_wrap.c -I/usr/local/include unix > ld -shared file_wrap.o -o Fileio.so
proc filecopy {name1 name2} {
set buffer [malloc 8192];
set f1 [fopen $name1 r];
set f2 [fopen $name2 w];
set nbytes [fread $buffer 1 8192 $f1];
while {$nbytes > 0} {
fwrite $buffer 1 $nbytes $f2;
set nbytes [fread $buffer 1 8192 $f1];
}
fclose $f1;
fclose $f2;
free $buffer
}
// SWIG file with variables and constants
%{
%}
// Some global variables
extern int My_variable;
extern char *default_path;
extern double My_double;
// Some constants
#define PI 3.14159265359
#define PI_4 PI/4.0
enum colors {red,blue,green};
const int SIZEOF_VECTOR = sizeof(Vector);
// A read only variable
%readonly
extern int Status;
%readwrite
%module tree
%{
#include "tree.h"
%}
class Tree {
public:
Tree();
~Tree();
void insert(char *item);
int search(char *item);
int remove(char *item);
static void print(Tree *t);
};
When translated, the class will be access used the following set of
functions (created automatically by SWIG).
All C++ functions wrapped by SWIG explicitly require the this pointer as shown. This approach has the advantage of working for all of the target languages. It also makes it easier to pass objects between other C++ functions since every C++ object is simply represented as a SWIG pointer. SWIG does not support function overloading, but overloaded functions can be resolved by renaming them with the SWIG %name directive as follows:Tree *new_Tree(); void delete_Tree(Tree *this); void Tree_insert(Tree *this, char *item); int Tree_search(Tree *this, char *item); int Tree_remove(Tree *this, char *item); void Tree_print(Tree *t);
class List {
public:
List();
%name(ListMax) List(int maxsize);
...
};
The approach used by SWIG is
quite different than that used in systems such as
Object Tcl or vtk [vtk,Wetherall]. As a result, users of those systems
may find it to be confusing. However,
It is important to note that the modular design of
SWIG allows the user to completely redefine the output behavior of
the system. Thus, while the current C++ implementation is quite different
than other systems supporting C++, it would be entirely possible
write a new SWIG module that wrapped C++ classes into a representation
similar to that used by Object Tcl (in fact, in might even be possible
to use SWIG to produce the input files used for Object Tcl).
%module package
%{
#include "package.h"
%}
%include geometry.i
%include memory.i
%include network.i
%include graphics.i
%include physics.i
%include wish.i
Common operations can be placed into a SWIG library for use in
all applications. For example, the %include wish.i directive
tells SWIG to include code for the Tcl_AppInit() function
needed to rebuild the wish program. The library can also be
used to build modules
allowing SWIG to be used with common Tcl extensions such
as Expect [Expect]. Of course, the primary use of the library is with
large applications such as Open-Inventor which contain hundreds of
modules and a substantial class hierarchy [Invent]. In this case a user
could use SWIG's include mechanism to selectively pick which modules
they wanted to use for a particular problem.
extern size_t fread(void *ptr, size_t size,
size_t nobj, FILE *stream);
/* {\tt fread} reads from {\tt stream} into
the array {\tt ptr} at most {\tt nobj} objects
of size {\tt size}. {\tt fread} returns
the number of objects read. */
When output by SWIG and processed by LaTeX, this appears as follows :
// File : swigtcl.h
class TCL : public Language {
private:
// Put private stuff here
public :
TCL();
int main(int, char *argv[]);
void create_function(char *,char *,DataType*,
ParmList *);
void link_variable(char *,char *,DataType *);
void declare_const(char *,int,char *);
void initialize(void);
void headers(void);
void close(void);
void usage_var(char *,DataType*,char **);
void usage_func(char *,DataType*,ParmList*,
char **);
void usage_const(char *,int,char*,char**);
void set_module(char *);
void set_init(char *);
};
Descriptions of these functions can be found in the SWIG
users manual. To build a new version of SWIG, the user
only needs to provide the function definitions and a main
program which looks something like the following :
// SWIG main program
#include "swig.h"
#include "swigtcl.h"
int main(int argc, char **argv) {
Language *lang;
lang = new TCL;
SWIG_main(argc,argv,lang,(Documentation *) 0);
}
When linked with a library file, any extensions and
modifications can now be used with the SWIG parser. While
writing a new language definition is not entirely trivial,
it can usually be done by just copying one of the existing
modules and modifying it appropriately.
/* File : list.i */
%{
struct Node {
Node(char *n) {
name = new char[strlen(n)+1];
strcpy(name,n);
next = 0;
};
char *name;
Node *next;
};
%}
// Just add struct definition to
// the interface file.
struct Node {
Node(char *);
char *name;
Node *next;
};
When used in a Tcl script, we can now create new nodes and access
individual members of the Node structure. In fact, we can
write code to convert between Tcl lists and linked lists entirely
in Tcl as shown :
# Builds linked list from a Tcl list
proc buildlist {list head} {
set nitems [llength $list];
for {set i 0} {$i < $nitems} {incr i -1} {
set item [lrange $list $i $i]
set n [new_Node $item]
Node_set_next $n $head
set head $n
}
return $head
}
# Builds a Tcl list from a linked list
proc get_list {llist} {
set list {}
while {$llist != "NULL"} {
lappend list [Node_name_get $llist]
set llist [Node_get_next $llist]
}
return $list
}
When run interactively, we could now use our Tcl functions as
follows.
% set l {John Anne Mary Jim}
John Anne Mary Jim
% set ll [buildlist $l _0_Node_p]
_1000cab8_Node_p
% get_list $ll
Jim Mary Anne John
% set ll [buildlist {Mike Peter Dave} $ll]
_1000cc38_Node_p
% get_list $ll
Dave Peter Mike Jim Mary Anne John
%
Aside from the pointer values, our script acts like any other
Tcl script. However, we have built up a real
C data structure that could be easily passed to other C functions
if needed.
%{
#include "nodes.h"
%}
%include wish
extern Node *new_node();
extern void AddEdge(Node *n1, Node *n2);
Within a Tcl/Tk script, loosely based on one to make ball and stick graphs
in [Ousterhout], a graph could be built as follows:
proc makeNode {x y} {
global nodeX nodeY nodeP edgeFirst edgeSecond
set new [.create oval [expr $x-15] \
[expr $y-15] [expr $x+15] \
[expr $y+15] -outline black \
-fill white -tags node]
set newnode [new_node]
set nodeX($new) $x
set nodeY($new) $y
set nodeP($new) $newnode
set edgeFirst($new) {}
set edgeSecond($new) {}
}
proc makeEdge {first second} {
global nodeX nodeY nodeP edgeFirst edgeSecond
set x1 $nodeX($first); set y1 $nodeY($first)
set x2 $nodeX($second); set y2 $nodeY($second)
set edge [.c create line $x1 $y1 $x2 $y2 \
-tags edge}
.c lower edge
lappend edgeFirst($first) $edge
lappend edgeSecond($first) $edge
AddEdge $nodeP($first) $nodeP($second)
}
These functions create
Tk canvas items, but also attach a pointer to a C data structure to each one.
This is done by maintaining an associative array mapping
item identifiers to pointers (with the nodeP() array).
When a
particular ``node'' is referenced later, we can use this to
get its pointer use it in calls to C functions.
// A SWIG inheritance example
%module shapes
%{
#include "shapes.h"
%}
class Shape {
private:
double xc, yc;
public:
virtual double area() = 0;
virtual double perimeter() = 0;
void set_position(double x, double y);
void print_position();
};
class Circle: public Shape {
private:
double radius;
public:
Circle(double r);
double area();
double perimeter();
};
class Square : public Shape {
private:
double width;
public:
Square(double w);
double area();
double perimeter();
};
Now, when wrapped by SWIG (note :
When parsing C++ classes, SWIG throws away everything declared as private,
inline code, and alot of the other clutter found in C++ header files.
Primarily this is provided only to make it easier to build interfaces from
existing C++ header files.), we can use our class structure as follows:
In our example, we have created new Circle and Square objects, but these can be used interchangably in any functions defined in the Shape base class. The SWIG type checker is encoded with the class hierarchy and knows the relationship between the different classes. Thus, while an object of type Circle is perfectly acceptable to a function operating on shapes, it would be unacceptable to a function operating only on the Square type. As in C++, any functions in the base class can be called in the derived class as shown by the Circle_print_position function above.% set c [new_Circle 4] _1000ad70_Circle_p % set s [new_Square 10] _1000adc0_Square_p % Shape_area $c 50.26548246400000200 % Shape_area $s 100.00000000000000000 % Shape_set_position $c -5 10 % Circle_print_position $c xc = -5, yc = 10 %
%module opengl
%{
#include <GL/gl.h>
%}
... Copy edited gl.h here ...
Early work has also been performed on using SWIG to wrap portions of the Open-Inventor package [Invent]. This is a more ambitious project since Open-Inventor consists of a very large collection of header files and C++ classes. However, the SWIG library mechanism can be used effective in this case. For each Inventor header, we can create a sanitized SWIG interface file (removing alot of the clutter found in the header files). These interface files can then be organized to mirror the structure of the Inventor system. To build a module for Tcl, a user could simply specify which modules they wanted to use as follows :% ... open GL widget here ... % glClearColor 0.0 0.0 0.0 0.0 % glClear $GL_COLOR_BUFFER_BIT % glColor3f 1.0 1.0 1.0 % glOrtho -1.0 1.0 -1.0 1.0 -1.0 1.0 % glBegin $GL_POLYGON % glVertex2f -0.5 -0.5 % glVertex2f -0.5 0.5 % glVertex2f 0.5 0.5 % glVertex2f 0.5 -0.5 % glEnd % glFlush
%module invent
%{
... put headers here ...
%}
%include "Inventor/Xt/SoXt.i"
%include "Inventor/Xt/SoXtRenderArea.i"
%include "Inventor/nodes/SoCone.i"
%include "Inventor/nodes/SoDirectionalLight.i"
%include "Inventor/nodes/SoMaterial.i"
%include "Inventor/nodes/SoPerspectiveCamera.i"
%include "Inventor/nodes/SoSeparator.i"
While wrapping the Inventor library will require significantly
more work than Open-GL, SWIG can be used effectively with such
systems.