The core of the decimal clock program is a new Tcl/Tk command,
DecimalTime, that returns the current time of day
as a decimal number of hours.
This new command is written in C, using the special
ET_PROC construct of ET. The code looks like this:
#include "tcl.h"
#include <time.h>
ET_PROC( DecimalTime ){
struct tm *pTime; /* The time of day decoded */
time_t now; /* Number of seconds since the epoch */
now = time(0);
pTime = localtime(&now);
sprintf(interp->result,"%2d.%03d",pTime->tm_hour,
(pTime->tm_sec + 60*pTime->tm_min)*10/36);
return ET_OK;
}
The magic is in the ET_PROC keyword.
The et2c preprocessor recognizes this keyword and
converts the code that follows it into a compilable C function that
implements the Tcl/Tk command.
In general, you can create new Tcl/Tk commands using a
template like this:
ET_PROC( name-of-the-new-command ){
/* C code to implement the command */
}
You could, of course, construct approprate C functions by hand, but
that involves writing a bunch of messy details that detract
from the legibility of the code.
The ET_PROC mechanism is much easier to write and
understand, and much less subject to error.
Though they do not appear explicitly in the source code, every
function created using ET_PROC has four formal parameters.
This parameter is an integer that holds the number of arguments
on the Tcl command that invokes the function.
Its role is exactly the same as the argc parameter to
the main() function of a standard C program.
Like argc before it, this parameter works just like the
argv parameter to main().
The variable argv[0] contains the name of the the
command itself (``DecimalTime'' in this
example), argv[1] contains the name of the first
argument, argv[2] contains the name of the second
argument, and so forth up to argv[argc] which is a
null pointer.
This parameter is a pointer to the Tcl/Tk interpreter.
It has type ``Tcl_Interp*''.
The interp
parameter has many uses, but is most often used to set the
return value of the Tcl/Tk function.
(Note that you have to #include either
<tcl.h> or
<tk.h> somewhere in your
source file in order to use the interp parameter, since
one of these header files are needed to define the fields of the
Tcl_Interp structure.)
This is a pointer to the Tk_Window structure that defines
the main window (e.g. the ``.'' window) of the application.
It has a type of ``void*'' and will need to be
typecast before it is used.
On the other hand, it is seldom used, so this isn't normally a problem.
The decimal clock example uses the interp
formal parameter on the sixth line of the ET_PROC
function.
In particular, the DecimalTime function writes its
result (e.g. the time as a decimal number) into the result
field of interp.
It's OK to write up to about 200 bytes of text into
the result field of the interp parameter,
and that text will become the return value of the Tcl/Tk
command.
If you need to return more than about 200 bytes of text, then
you should set the result using one of the routines from the
Tcl library designed for that purpose:
Tcl_SetResult(),
Tcl_AppendResult(), or
Tcl_AppendElement().
(These routines are documented by Tcl's manual pages
under the name ``SetResult''.)
If all this seems too complicated, then you can choose to
do nothing at all, in which case
the return value defaults to an empty string.
Another important feature of every ET_PROC function is
its return value.
Every ET_PROC should return either ET_OK
or ET_ERROR, depending on whether or not the
function encountered any errors.
(ET_OK and ET_ERROR are #define
constants
inserted by et2c and have the save values as
TCL_OK and TCL_ERROR.)
It is impossible for the DecimalClock function to fail,
so it always returns ET_OK, but most ET_PROC
functions can return either result.
Part of Tcl's result protocol is that if a command
returns ET_ERROR it should put an error message in
the interp->result field.
If we had wanted to be pedantic, we could have put a test in the
DecimalTime function to make sure it is called with
no arguments.
Like this:
ET_PROC( DecimalTime ){
struct tm *pTime; /* The time of day decoded */
time_t now; /* Number of seconds since the epoch */
if( argc!=1 ){
Tcl_AppendResult(interp,"The ",argv[0],
" command should have no argument!",0);
return ET_ERROR;
}
/* The rest of the code is omitted ... */
}
New Tcl/Tk commands that take a fixed format normally
need to have some checks like this, to make sure they aren't called
with too many or too few arguments.