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: | https://www.usenix.org |
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).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; }
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 :/* 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 *);
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
When translated, the class will be access used the following set of functions (created automatically by SWIG).%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); };
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);
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).class List { public: List(); %name(ListMax) List(int maxsize); ... };
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.%module package %{ #include "package.h" %} %include geometry.i %include memory.i %include network.i %include graphics.i %include physics.i %include wish.i
When output by SWIG and processed by LaTeX, this appears as follows :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. */
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 :// 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 *); };
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.// 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 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 :/* 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 run interactively, we could now use our Tcl functions as follows.# 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 }
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.% 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 %
Within a Tcl/Tk script, loosely based on one to make ball and stick graphs in [Ousterhout], a graph could be built as follows:%{ #include "nodes.h" %} %include wish extern Node *new_node(); extern void AddEdge(Node *n1, Node *n2);
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.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) }
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:// 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(); };
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
While wrapping the Inventor library will require significantly more work than Open-GL, SWIG can be used effectively with such systems.%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"