Combining C and tcl/tk


Topics

Introduction
Compile Options
Initialization and Registering Commands
Accessing Variables

Introduction

Programming for X can be cumbersome, in comparison tcl/tk provides a quick and easy way of getting a graphical application up and running. Tcl/tk is a scripting language, and as such there are some things you just can't do, or can't do easily. The answer is to use a hybrid of C and tcl/tk. The tcl/tk system comes with libraries which allow a C program to call the tcl/tk interpreter and run tcl/tk scripts.

The provided library includes methods for initializing the environment, calling different scripts, and accessing variables. Using this hybrid environment also benefits the programmer by giving them access to features which are inherent in X. Simple call back and timer functions allow the programmer to schedule events, and the ability to register a C function as a procedure in tcl/tk creates a powerful tool.

This document covers the basics of integrating tcl/tk scripts with C. The Compile Options section describes the various libraries and include files necessary to create a program. The Initialization and Registering Commands section explains how to get started, and how to call a C function from a tcl/tk script. The last section Accessing Variables explains how to read and write tcl/tk variables from functions in C.

Back to the top


Compile Options

In order to access the tcl/tk library routines several things have to be done to your source code and how you compile it. There are two include files which have the declarations for the calls to the library:

#include <tcl.h>
#include <tk.h>
Compiling for a hybrid application requires pointing the compiler at the right include directories, the right libraries, and setting the right link flags. On top of settings for tcl/tk it is also necessary to include files and libraries for X. The following flags are those to be set when using g++, your own system may vary depending on compiler and file locations.
-I/software/tcl-7.4/include
-I/software/tk-4.0/include
-I/software/x11r5_dev/Include
-L/software/tcl-7.4/lib
-L/software/tk-4.0/lib
-L/software/x11r5_dev/lib
-ltk
-ltcl
-lX11

Back to the top


Initialization and Registering Commands

Creating a hybrid tcl/tk & C application centres around a few choice commands. The first of these is the "Tk_Main" function, which is used to hand over control of the program to the tcl/tk interpreter. This command does not return, so it should be the last line in your "main" function, once all of your program's initialization is done.

The "Tk_Main" function takes three parameters. The second parameter is an array of strings, each string having a special meaning. The first parameter is the number of indices in this array of strings. The third parameter is a pointer to a function which is called for initialization. This initialization function is where most of the work gets done.

The array of strings which is passed to "Tk_Main" informs the tcl/tk interpreter of the name of the application and the location of the script which has the tcl/tk commands in it. This array is actually the command line parameters which are passed to the wish-like interpreter. The first item in the array gives the name of the application, and the second is the location of the script to be run. If the script is not in the same directory as the executable, it is wise to use a fully qualified path.

Due to legacy reasons, tcl/tk requires that the strings passed into most of its functions be modifiable, it also has the occasional problem with function scope. The easiest way to avoid these problems is to dynamically allocate the strings being passed in. The following code fragment shows a call to "Tk_Main" using the application "Hello World" and the script "hello.tcl".

// prototype for the initialization function
int InitProc( Tcl_Interp *interp );

// declare an array for two strings
char *ppszArg[2];

// allocate strings and set their contents
ppszArg[0] = (char *)malloc( sizeof( char ) * 12 );
ppszArg[1] = (char *)malloc( sizeof( char ) * 12 );
strcpy( ppszArg[0], "Hello World" );
strcpy( ppszArg[1], "./hello.tcl" );

// the following call does not return
Tk_Main( 2, ppszArg, InitProc );

Initialization Functions

The call to "Tk_Main" hands over control of your program to the tcl/tk interpreter, but after some base initialization is done and before the tcl/tk script is run a user defined function can be executed. The above example shows a function of this type: "InitProc". This user defined initialization function must return an integer and takes one parameter Tcl_Interp *, a pointer to an interpreter.

Inside of the initialization function is where you create an actual interpreter with a call to "Tk_Init". The "Tk_Init" function takes one parameter and that is a pointer to an interpreter, this should just be the pointer passed into your initialization function. The following code is a bare bones initialization function, more will be added later.

int InitProc( Tcl_Interp *interp )
{
int iRet;

// Initialize tk first
iRet = Tk_Init( interp );
if( iRet != TCL_OK)
{
fprintf( stderr, "Unable to Initialize TK!\n" );
return( iRet );
} // end if

return( TCL_OK );
} // end InitProc

C Functions as tcl/tk procedures

By now you are familiar with procedure calls inside of tcl/tk script. It is possible when programming a hybrid application to have a tcl/tk procedure call a C function. To accomplish this requires a call to the "Tcl_CreateCommand" function. This is normally done inside of the initialization function. Calling the function as a procedure in tcl/tk is just like calling any other procedure. No declaration for the procedure should exist in the tcl/tk script.

Functions which are to be registered as procedures have a very specific prototype. They must return an integer, and take four arguments. The first argument is of type "ClientData" which is a tcl/tk library type. The second argument is a pointer to the interpreter. The last two arguments are similar to the "argc" and "argv" arguments in a C "main" function. These two arguments are used to pass the parameters given to the tcl/tk procedure. The "argc" argument contains the number parameters passed to the tcl/tk procedure, and the "argv" is an array of strings, each string containing a parameter.

int Myfunc( ClientData Data, Tcl_Interp *pInterp, int argc, char *argv[] );
When a function is registered to be used as a tcl/tk procedure it can have a pointer associated with it, this pointer is passed in as the "ClientData". The concept of "ClientData" allows the programmer to associate a data structure with a tcl/tk object, and calls to procedures can reference this object. This structure is not often needed.

As was mentioned earlier the registration process requires a call to the "Tcl_CreateCommand" function. This function takes five arguments. The first argument is a pointer to an interpreter. The second argument is a string which is the name of the tcl/tk procedure. The third argument is a pointer to the function which is called when the tcl/tk procedure is invoked. The last two arguments are the "ClientData" item, and a pointer to a deletion routine. The deletion routine allows a C function to be called when the program exits in order to clean up structures associated with objects. Like "ClientData" the pointer to the deletion function is not often necessary. The following is a sample registration of the tcl/tk procedure called "hey_there" which is to call the above declared "Myfunc"

Tcl_CreateCommand( interp, "hey_there", Myfunc, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL );

Back to the top


Accessing Variables

Being able to call a C function by invoking a tcl/tk procedure allows you to have tcl/tk get help from C. In order to have C get help from tcl/tk there are a series of functions, the ones covered here will deal with getting information from, and putting information into a tcl/tk variable.

Tcl_GetVar

The "Tcl_GetVar" function returns a pointer to a string which contains the contents of the specified tcl/tk variable. The function takes three arguments, a pointer to the interpreter, a string with the name of the tcl/tk variable, and a flag. The variable that is accessed is at the current scope in the executing script associated with the interpreter. If there is no variable at the current scope level with the given name then global variables are checked. If no matching global variable is found an error is returned. The flags argument allows you to specify TCL_GLOBAL_ONLY, in order to force the function to only check for global variables with the given name. The following code is part of a tcl/tk script that will be accessed.

set say_hello_to "World"
The following code is a the call in C to access the tcl/tk variable "say_hello_to".
char sHelloTo[30];

// after this call sHelloTo should contain "World"
strncpy( sHelloTo, Tcl_GetVar( pInterp, "say_hello_to", 0 ), 29 );

Tcl_SetVar

The "Tcl_SetVar" function allows the programmer to change the contents of a tcl/tk variable. The function takes four arguments, the first is a pointer to the interpreter, the second a string indicating the name of the tcl/tk variable to change, the third is a string with the new value for the variable, and the last argument is for flags. The "Tcl_SetVar" flags are the same as for "Tcl_GetVar". The "Tcl_SetVar" function returns NULL if an error occurred during the setting. If the variable does not exist, this function will create the variable locally to the scope currently in execution in the script referenced by the interpreter pointer. The following code sets a tcl/tk variable called "say_hello_to" to contain the value "World".

Tcl_SetVar( pInterp, "say_hello_to", "World", 0 );

Back to the top