Extending Tcl is a guide to writing Tcl extensions. The Tcl C API can be used to extend Tcl with new commands. The Tcl Extension Architecture TEA is a set of guidelines and techniques for the distribution, configuration, compilation, and installation of Tcl extensions. TEA comes with a "sample extension" showing how to do things:
For information at building Tcl extension using Cygnus's Cygwin environment, take a look at the following files:
An extension is normally compiled and linked against the Tcl stubs static archive, not the Tcl shared object. After writing the desired function in C, add an initialisation function, to be called by load, that typically initialises the Tcl stubs tables and creates new Tcl commands with Tcl_CreateCommand or better, Tcl_CreateObjCommand.
The name of the initialisation function is derived from the name of the shared object. In this case the name of the shared object is tclextinfo sharedlibextension, and therefore load looks for a function called Tclext_Init. Note the initial T has been capitalised.
/* This is your useful procedure. It can be a C++ or C linked procedure. */ int tclextfunction ( ClientData clientData, Tcl_Interp *interp, int argc, char *argv[]) { /* dosomething - clientdata is a pointer to something the programmer may or may not define */ return TCL_OK; } #ifdef __cplusplus extern "C" { #endif int Tclext_Init(Tcl_Interp *interp) { /* Tcl stubs and tk stubs are needed for dynamic loading, you must have this set as a compiler option */ #ifdef USE_TCL_STUBS if (Tcl_InitStubs(interp, TCL_VERSION, 1) == NULL) { Tcl_SetResult(interp, "Tcl_InitStubs failed",TCL_STATIC); return TCL_ERROR; } #endif #ifdef USE_TK_STUBS if (Tk_InitStubs(interp, TCL_VERSION, 1) == NULL) { Tcl_SetResult(interp, "Tk_InitStubs failed", TCL_STATIC); return TCL_ERROR; } #endif /* register your functions with Tcl */ Tcl_CreateCommand( "tclextfunction", tclextfunction ); return TCL_OK; } #ifdef __cplusplus } #endif
Then, in Tcl script use
load tclext[info sharedlibextension] tclextfunction a b c
to pass the strings a,b,c to tclextfunction in the array argv - argc will be 3 of course. The ClientData is a pointer to anything (perhaps a global data structure or a class defined by a ClientData statement often dynamically allocated in the Tclext_Init function).
Reasons for extending Tcl include exposing an external API, and increasing the speed of a CPU-intensive calculation.
Probably the easiest way to call C from Tcl is via CriTcl. CriTcl allows you to embed C functions within a Tcl script, compiling and cached the results the first time they are invoked. From then on the compiled C code will be dynamically loaded. You can also use CritBind to pre-build libraries or executables.
stevel: See CriTcl builds C extensions on-the-fly for more information.
The return value of a C function implementing a Tcl command is a status code that is typically either Tcl_OK or TCL_ERROR (there are also TCL_BREAK, TCL_CONTINUE and TCL_RETURN for special cases). The function can also set the result of the script-level procedure by calling functions such as Tcl_SetResult/Tcl_SetObjResult, Tcl_AppendResult, Tcl_AppendElement together with Tcl_GetObjResult and Tcl_GetStringResult.
Note that interp->result is now forbidden.
(putatively) DKF:
If you want to learn how to write a Tcl command in C, one of the easiest places to look is the Tcl sources themselves. Particularly, try looking in .../generic/tclCmd??.c. For example, look at the definition of the source in the function Tcl_SourceObjCmd, in tclCmdMZ.c. If there's a simpler command than that in the sources, I don't know of it.
An example:
#include <tcl.h> int Md5CryptCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { Tcl_Obj *saltObj, *passwordObj; char tmpBuffer[TMP_BUF_SIZE]; /* Or whatever! */ if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "salt password"); return TCL_ERROR; } saltObj = objv[1]; passwordObj = objv[2]; yourFunctionToDoTheRealWork(Tcl_GetString(saltObj), Tcl_GetString(passwordObj), &tmpBuffer); Tcl_SetResult(interp, tmpBuffer, TCL_VOLATILE); return TCL_OK; }
OTOH, using Tcl_GetIntFromObj() is a little more complex because that can fail (e.g. when the argument is something like "foobar" )
int FoobarCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { int foo, bar, resultValue; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "foo bar"); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[1], &foo) != TCL_OK || Tcl_GetIntFromObj(interp, objv[2], &bar) != TCL_OK) { /* There's already a nice error message */ return TCL_ERROR; } resultValue = yourFunctionToDoTheRealWork(foo, bar); /* There's other ways to do this, but they're not much more efficient and are definitely less clear */ Tcl_SetObjResult(interp, Tcl_NewIntObj(resultValue)); return TCL_OK; }
The Tcl C API is powerful and for something like a new data type for a tree, threads, or other things that can not be easily done with executable modules it's a good solution. There are at least 2 approaches to extending Tcl via the C API. A new tclsh-like shell can be created, or a loadable extension can be loaded into an interpreter.
Our first example will be a new interactive shell that performs simple addition. This will teach you how to create a Tcl interpreter, initialize Tcl, and create a simple command for addition.
Pro:
Con:
#include <stdio.h> #include <stdlib.h> #include <tcl.h> int AddObjCmd (ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { long n1; long n2; Tcl_Obj *res; /*The command name is objv[0] so 3 arguments are expected.*/ if (3 != objc) { Tcl_WrongNumArgs (interp, 1, objv, "n1 n2"); return TCL_ERROR; } if (TCL_OK != Tcl_GetLongFromObj (interp, objv[1], &n1)) { /*The error result should be set by Tcl_GetLongFromObj.*/ return TCL_ERROR; } if (TCL_OK != Tcl_GetLongFromObj (interp, objv[2], &n2)) { return TCL_ERROR; } res = Tcl_NewLongObj (n1 + n2); Tcl_SetObjResult (interp, res); return TCL_OK; } int main (int argc, char *argv[]) { Tcl_Interp *interp; /* * This finds Tcl's library files and performs some initialization. */ Tcl_FindExecutable (argv[0]); interp = Tcl_CreateInterp (); if (TCL_OK != Tcl_Init (interp)) { fprintf (stderr, "Tcl_Init error: %s\n", Tcl_GetStringResult (interp)); exit (EXIT_FAILURE); } Tcl_CreateObjCommand (interp, "+", AddObjCmd, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); while (1) { char cmd[1024]; fgets (cmd, sizeof (cmd), stdin); if (TCL_OK != Tcl_Eval (interp, cmd)) { fprintf (stderr, "error: %s\n", Tcl_GetStringResult (interp)); continue; } printf ("result is: %s\n", Tcl_GetStringResult (interp)); } return EXIT_SUCCESS; }
[explain how to build an export list for a DLL]
[explain how to link using MSVC++]
The approach is essentially to write a wrapper function which calls your C code. This wrapper function is in a standard format that Tcl understands. You then call a Tcl function to "register" the wrapper function with the Tcl interpreter, creating a new command. After that, every time that command is called in the interpreter, it calls the wrapper function, which calls your C code.
See Extending Tcl for some pointers to some tools to help write the interface code for you. SWIG is often used, and is highly recommended. In fact, the example code in this section was generated by SWIG.
Here is a very simple C function which we will integrate into the Tcl interpreter. We will create a new command called square to call this function. The C code looks like this:
int square (int i) { return i*i; }
Back in the "good old days" (before Tcl 8.0) everything really was a string inside of the Tcl interpreter. That made the interface functions pretty simple. They are a little like a main() function, with argc and argv. Our interface code is pretty simple, copying the argument from argv, and returning a result string. .
#include <tcl.h> static int _wrap_square(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[]) { int _result; int _arg0; _arg0 = (int) atol(argv[1]); _result = (int )square(_arg0); sprintf(interp->result,"%ld", (long) _result); return TCL_OK; }
Note: This is just an example of a simple interface function. Don't do it this way any more, even though the backward-compatibility fanatics in the Tcl community continue to support all of the syntax. In particular, copying the result string into the interp->result field is not recommended. Bad things might happen.
At some point in our application, we have to start up a Tcl interpreter, and register our wrapper function with the interpreter. This is usually done with a function called Tcl_AppInit(). Once this initialization is complete, your application can execute scripts from strings or files by calling Tcl_Eval() or Tcl_EvalFile().
int Tcl_AppInit(Tcl_Interp *interp){ if (Tcl_Init(interp) == TCL_ERROR) return TCL_ERROR; /* Now initialize our functions */ Tcl_CreateCommand(interp, "square", _wrap_square, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); return TCL_OK; }
Call this initialization function somewhere in your main line. First create an interpreter, then call Tcl_AppInit(). You could do this all in one fell swoop, but the Tcl_AppInit() format is standard, and supports creating extended interpreter shells - where your application actually becomes a Tcl interpreter, which is Extremely Cool!(tm)
Tcl_Interp *interp; interp = Tcl_CreateInterp(); Tcl_AppInit(interp);
The more modern approach to integrating Tcl applications depends on the object interface supported in Tcl versions 8.0 and higher. The object model supports the on-the-fly bytecode compiler, and is very efficient for integrating C code. It is not necessary to shimmer values back and forth to strings. Unfortunately, the object interface is a little more complex, with special functions to convert from Tcl_Objs to basic types. You should also do a little more careful parsing of arguments, and even generate error messages if the Tcl command is malformed. Again, SWIG is a great tool that can generate all this for you. But you can still do it yourself. The new object interface function might look like this:
static int _wrap_square(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { int _result; int _arg0; Tcl_Obj * tcl_result; int tempint; clientData = clientData; objv = objv; tcl_result = Tcl_GetObjResult(interp); if ((objc < 2) || (objc > 2)) { Tcl_SetStringObj(tcl_result,"Wrong # args. square i ",-1); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp,objv[1],&tempint) == TCL_ERROR) return TCL_ERROR; _arg0 = (int ) tempint; _result = (int )square(_arg0); tcl_result = Tcl_GetObjResult(interp); Tcl_SetIntObj(tcl_result,(long) _result); return TCL_OK; }
And, you would, of course, register this new command as an object command. The rest of the initialization remains the same
Tcl_CreateObjCommand(interp, SWIG_prefix "square", _wrap_square, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
Using C++ with Tcl is similar to using it with C. There are a few tricks to using methods within a class. Within the class we create a wrapper which calls a static function within the class.
#include <stdio.h> #include <stdlib.h> /*This is specifically for C++.*/ extern "C" { #include <tcl.h> } /*Thanks to Kevin Kenny for his help with this.*/ class Math { public: static int AddObjCmd (ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { return (reinterpret_cast<Math*>(clientData))->AddMethod (interp, objc, objv); } int AddMethod (Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]); }; int Math::AddMethod (Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { long n1; long n2; Tcl_Obj *res; /*The command name is objv[0] so 3 arguments are expected.*/ if (3 != objc) { Tcl_WrongNumArgs (interp, 1, objv, "n1 n2"); return TCL_ERROR; } if (TCL_OK != Tcl_GetLongFromObj (interp, objv[1], &n1)) { /*The error result should be set by Tcl_GetLongFromObj.*/ return TCL_ERROR; } if (TCL_OK != Tcl_GetLongFromObj (interp, objv[2], &n2)) { return TCL_ERROR; } res = Tcl_NewLongObj (n1 + n2); Tcl_SetObjResult (interp, res); return TCL_OK; } int main (int argc, char *argv[]) { Tcl_Interp *interp; /*This finds Tcl's library files and performs some initialization.*/ Tcl_FindExecutable (argv[0]); interp = Tcl_CreateInterp (); if (TCL_OK != Tcl_Init (interp)) { fprintf (stderr, "Tcl_Init error: %s\n", Tcl_GetStringResult (interp)); exit (EXIT_FAILURE); } Math *inst = new Math; Tcl_CreateObjCommand (interp, "+", inst->AddObjCmd, (ClientData) inst, (Tcl_CmdDeleteProc *) NULL); while (1) { char cmd[1024]; fgets (cmd, sizeof (cmd), stdin); if (TCL_OK != Tcl_Eval (interp, cmd)) { fprintf (stderr, "error: %s\n", Tcl_GetStringResult (interp)); continue; } printf ("result is: %s\n", Tcl_GetStringResult (interp)); } return EXIT_SUCCESS; }
[perhaps someone more knowledgeable about C++ could improve this]
SLB I'll just comment that the 'tricks' are dangerous. Compiling the above code with Sun's compiler reports:
"tclmain.cpp", line 61: Warning (Anachronism): Formal argument proc of type extern "C" int(*)(void*,Tcl_Interp*,int,Tcl_Obj*const*) in call to Tcl_CreateObjCommand(Tcl_Interp*, const char*, extern "C" int(*)(void*,Tcl_Interp*,int,Tcl_Obj*const*), void*, extern "C" void(*)(void*)) is being passed int(*)(void*,Tcl_Interp*,int,Tcl_Obj*const*).
I don't have a reference to the relavent clause in the C++ standard to hand but warnings about portability problems from treating C and C++ function pointers as interchangeable are widespread. See for example [L2 ]
Links:
To expose an Objective-C class to Tcl we can create a wrapper in much the same was as was done with C++ earlier.
Compile this code using something like:
gcc tcl_objc.m -lobjc -I/usr/pkg/include -L/usr/pkg/lib -ltcl84 -lm -lpthread -Wl,-rpath -Wl,/usr/pkg/lib
tcl_objc.h:
/* * By George Peter Staplin * This is version 2, and much better than the previous code I put here... */ #include <stdio.h> #include <stdlib.h> #include <tcl.h> #include <objc/Object.h> @interface Adder : Object { /* This is just a pointless instance variable. */ int lastresult; } - (int) add:(Tcl_Interp *)interp x:(Tcl_Obj *)x y:(Tcl_Obj *)y; @end int AdderWrapper (ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[]);
tcl_objc.m:
#include "tcl_objc.h" @implementation Adder - (int)add:(Tcl_Interp *)interp x:(Tcl_Obj *)x y:(Tcl_Obj *)y { int a, b; if (TCL_OK != Tcl_GetIntFromObj (interp, x, &a)) return TCL_ERROR; if (TCL_OK != Tcl_GetIntFromObj (interp, y, &b)) return TCL_ERROR; Tcl_SetObjResult (interp, Tcl_NewIntObj (lastresult = a + b)); return TCL_OK; } @end int AdderWrapper ( ClientData cdata, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { id inst = (id) cdata; if (TCL_OK != [inst add: interp x: objv[1] y: objv[2]]) { return TCL_ERROR; } return TCL_OK; } int main (int argc, char *argv[]) { Tcl_Interp *interp; id inst; Tcl_FindExecutable (argv[0]); interp = Tcl_CreateInterp (); if (TCL_OK != Tcl_Init (interp)) { fprintf (stderr, "Tcl_Init error: '%s'\n", Tcl_GetStringResult (interp)); return EXIT_FAILURE; } inst = [Adder new]; Tcl_CreateObjCommand (interp, "add", AdderWrapper, (ClientData) inst, (Tcl_CmdDeleteProc *) NULL); if (TCL_OK != Tcl_Eval (interp, "add 200 123")) { fprintf (stderr, "Tcl_Eval error: '%s'\n", Tcl_GetStringResult (interp)); return EXIT_FAILURE; } printf ("Success: the result of add 200 123 is: %s\n", Tcl_GetStringResult (interp)); return EXIT_SUCCESS; }
jcw 2003-01-15: Your "add" example above in Critcl:
package require critcl critcl::cproc addup {long a long b} long { return a + b; } interp alias {} + {} addup puts "123 + 864 = [+ 123 864]"
If you have tclkit and the critcl starkit, do:
$ critcl add.tcl 123 + 864 = 987
If you want to turn this into a loadable package, use this code instead:
package provide add 1.0 package require critcl critcl::cproc addup {long a long b} long { return a + b; }
Then use the -pkg flag:
$ critcl -pkg add.tcl Source: add.tcl Library: add.so Package: /home/jcw/lib/add $
The result is a self-contained auto-generated package:
$ ls -lR lib/add/ lib/add/: total 9 drwxr-xr-x 2 jcw users 104 Jan 15 18:52 Linux-x86 -rw-r--r-- 1 jcw users 1506 Jan 15 18:52 critcl.tcl -rw-r--r-- 1 jcw users 64 Jan 15 18:52 pkgIndex.tcl lib/add/Linux-x86: total 12 -rwxr-xr-x 1 jcw users 4660 Jan 15 18:50 add.so -rw-r--r-- 1 jcw users 157 Jan 15 18:52 critcl.tcl $