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.