

1.  Embedded Tk

If you've ever tried to build a large-scale, compute-intensive or
commercial application using Tcl/Tk, you probably had a difficult time
of it.  A pure Tcl/Tk script is terrific for writing small programs or
for prototyping, but it is often inadequate for really big problems.
This is due to several factors:

  *  Execution speed is usually too slow for serious computation.
  *  Complex data structures are difficult to implement in Tcl.
  *  The lack of structure and typing in the Tcl language complicates
     the development of large codes.
  *  Tcl/Tk source is easily read by the end user, making it hard to
     protect proprietary algorithms.
  *  A script will only run on machines where the correct version of
     Tcl/Tk has been installed.  This makes scripts more difficult to
     distribute.

The usual way to avoid these troubles is to code in C or C++ rather
than Tcl/Tk. C is fast and well-structured.  Compiled C code is
difficult for users to read.  And, statically-linked C programs will
run on any binary-compatible computer, independent of other software.

But programming a graphical user interface in pure C is time-consuming
and error-prone.  The job can be made somewhat easier by using
Tcl/Tk's C interface, and having your C program call the Tcl/Tk
library routines directory.  Many people have done this, some
successfully.  The task is still tough, though, because unlike its
scripting language, Tcl/Tk's C interface is not easy to use.  Properly
initializing the Tcl/Tk interpreter takes skill and finesse, and
calling the interpreter from C is a dull chore.

And so the problem remains: Do you go for the speed and structure of C
or the power and simplicity of Tcl/Tk?

The Embedded Tk system (hereafter "ET") was created to resolve this
conundrum.  ET is a simple preprocessor and small interface library
that make it easy to mix Tcl/Tk and C in the same program.  With ET,
you can put a few commands of Tcl/Tk code in the middle of a C
routine.  ET also makes it very easy to write C functions that work as
new Tcl/Tk commands -- effectively allowing you to put pieces of C
code in the middle of your Tcl. These features gives you the speed and
structure of C with the power and simplicity of Tcl/Tk. As an added
benefit, an application written using ET will compile into a
stand-alone executable that will run on any binary-compatible
computer, even if the other computer doesn't have Tcl/Tk installed.


2.  An Example: ``Hello, World!''

The ET system is designed to be easy to use.  To see this, let's look
at the classic "Hello, World!" program, coded using ET.

    void main(int argc, char **argv){
      Et_Init(&argc,argv);
      ET( button .b -text {Hello, World!} -command exit; pack .b );
      Et_MainLoop();
    }

If you compile and link these 5 lines, you'll get a stand-alone
executable that pops up a "Hello, World!" button, and goes away when
the button is clicked.

Let's take this program apart to see how it works.  The first thing it
does is call the Et_Init() procedure.  This procedure performs the
tedious and confusing work needed to start up the Tcl/Tk interpreter,
initialize widget bindings, create the main window ".", and so forth.
The last line is a call to another procedure Et_MainLoop() than
implements the event loop.  (If you don't know what an event loop is,
don't worry.  We'll have more to say about event loops in section 4.)
The most interesting part of the example is the middle line, the one
that looks like a call to a function named ET(). The ET() function is
special.  It looks and is used like a regular C function, but takes a
Tcl/Tk script as its argument, instead of a C expression.  What it
does is execute the enclosed Tcl/Tk. In this particular example, the
ET() function creates the "Hello, World!" button.

Because of the ET() function, we can't give the "Hello, World!" source
code directly to a C compiler and expect it to work.  We have to run
it through a preprocessor first.  Like this:

    et2c hello.c <hello_.c

The et2c preprocessor converts the ET() function into real, compilable
C code.  The preprocessor also takes care of some other housekeeping
details, like adding prototypes to the top of the file so that we
don't have to bother with a "#include".  After it has been
preprocessed, the source code can be compiled like any other C
program.

    cc -O -o hello hello_.c et.o -ltk -ltcl -lXll -lm

Notice that you must link the program with ET's et.o library file, and
with libraries for Tcl/Tk and X11. (See section 13 for instructions on
building applications for MS-Windows or Macintosh.)

And that's all there is too it!


3.  How To Avoid Reading The Rest Of This Article

If you're restless to start programming and are the type of person who
prefers to learn at the keyboard rather then from a book, then this
section is for you.  It contains a terse overview of the features of
ET. Peruse this section, glance quickly at the figures, and you'll be
ready to start coding.  You can use the rest of the article as a
reference guide when you run into trouble.

On the other hand, if you are new to graphical interface programming,
are a little unsteady with C, or just have a more deliberate and
cautious attitude toward life, then you may prefer to lightly skim or
even skip this section and focus instead on the tutorial-like text
that follows.

The ET system consists of two things: the et2c preprocessor and the
et.o library.  The preprocessor takes care of translating ET source
code (which looks a whole lot like C) into genuine C code that your
compiler will understand.  The et.o library contains a few support
routines.

Among the support routines in et.o are Et_Init() and Et_MainLoop() for
initializing the ET package and implement the event loop,
respectively.  A third routine Et_ReadStdin() allows standard input to
be read and interpreted by the Tcl/Tk interpreter at run-time.  The
et.o library defines three global C variables as a convenience.
Et_Interp is a pointer to the Tcl/Tk interpreter used by ET.
Et_MainWindow is the main window, ".". Et_Display is the Display
pointer required as the first argument to many Xlib routines.  ET also
provides two global Tcl variables, cmd_name and cmd_dir.  These
contain the name of the executable and the directory where the
executable is found.

The et2c preprocessor is used to convert an ET source file in real C
code.  It creates the illusion of giving the C language some new
statements, like ET_INSTALL_COMMANDS and ET_PROC and some special new
functions like ET().

The ET() function is used as if it were a regular C function, except
that its argument is a Tcl/Tk script.  The job of the ET() is to
execute the script.  ET() returns either ET_OK or ET_ERROR depending
upon whether the script suceeded or failed.  Similar routines ET_STR(),
ET_INT() and ET_DBL() also take a Tcl/Tk script as their argument, but
return the string, the integer, or the double-precision floating point
number that was the result of the last Tcl/Tk command in the argument
script.

Wherever the string %d(x) occurs inside an ET() function, the integer
C expression x is converted to ASCII and substituted in place of the
%d(x).  Similarly, %s(x) can be used to substitute a character string,
and %f(x) will substitute a floating point value.  The string %q(x)
works like %s(x) except that a backslash is inserted before each
character that has special meaning to Tcl/Tk.

The special construct "ET_PROC( newcmd ){...}" defines a C function
that is invoked whenever the newcmd Tcl/Tk command is executed.
Formal parameters to this function, argc and argv, describe the
arguments to the command.  The formal parameter interp is a pointer to
the Tcl/Tk interpreter.  If a file named aux.c contains one or more
ET_PROC macros, then the commands associated with those macros are
registered with the Tcl/Tk interpreter by invoking 

procedure.

The statement "ET_INCLUDE( script.tcl )" causes the Tcl/Tk script in
the file script.tcl to be made a part of the C program and executed at
the point where the ET_INCLUDE() macro is found.  The external Tcl/Tk
script is normally read into the C program at compile-time and thus
becomes part of the executable.  However, if the -dynamic command-line
option is given to the et2c preprocessor, loading of the external
Tcl/Tk script is deferred to run-time.

Finally, at the top of its output files, the et2c preprocessor inserts
#defines that make ET_OK and ET_ERROR equivalent to TCL_OK and
TCL_ERROR. This often eliminates the need to put "#include <tcl.h>" at
the beginning of files that use ET.

And thats everything in ET! All the rest is just detail.


4.  A Quick Review Of Event Driven Programs

Before we delve into the details of ET, it may be helpful to review
the concept of an event loop and an event-driven program.  Many ET
users have never before written a event-driven graphical user
interface (GUI) and may be unfamiliar with how such programs operate.
If you are such a user, you may profit from this quick review.  But if
you are already familiar with event-driven programs, feel free skip
ahead to section 5.

The only inputs to a GUI are "events".  An event is a notification
that something interesting has happened.  Events arrive whenever the
mouse moves, or a mouse button is pressed or released, or a key of the
keyboard is pressed, and so forth.  A event-driven GUI differs from
more familiar command-line programs in that its inputs (e.g. events)
do not arrived in any predictable sequence.  Any kind of events can
arrive at any time, and the GUI program must be prepared to deal with
them.

The code for an event-driven GUI can be divided into two parts: the
initialization code and the event loop.  The initialization code runs
first and does nothing more than allocate and initialize the internal
data structures of the application.  As soon as the initialization
code completes, the application enters the event loop.  Within the
event loop, the program waits for the next event to arrive, reads the
event, and processes it appropriately.  The loop then repeats.  The
event loop does not exit until the program terminates.

This is a schematic view of a typical GUI program:

   main(){
      /* Initialization code */
      while( /* More work to do */ ){
        /* Wait for the next event to arrive */
        /* Read the next event */
        /* Take appropriate action for the event just read */
      }
   }

Don't worry about the details here.  Most of the event loop processing
is handled automatically by Tcl/Tk and ET. The important things to
know are that the event loop exists, it runs after the initialization
code, and that it doesn't terminate until the program exits.

If you've never written an event-driven program before, and you are
like most people, then you will have a little trouble at first.  To
help you get started, here are some important points to remember:

  1. The initialization code does not interact with the user.

     The initialization code does only one thing -- initialize.  It
     creates the main windows of the application (but it doesn't draw
     the windows -- that happens in the event loop!) and it sets up
     internal data structures.  But the initialization code should
     never wait for input or respond to an event.  Waiting and reading
     inputs and responding to events should only happen in the event
     loop.

  2. All user-initiated processing occurs in callbacks

     Everything that a GUI program does will be in response to some
     event.  Any C procedure or Tcl/Tk command that is called in
     response to an event is referred to as a callback.  Because all
     inputs to a GUI program are in the form of events, the only place
     for user-initiated processing to occur is within the callback
     routines.

  3. Don't let a callback compute for more than a fraction of a second

     A callback should do its job quickly and then return.  Otherwise,
     the event loop will not be able to respond to new events as they
     arrive, and the program will appear to "hang".  If you have a
     callback that needs to execute for more than a few hundred
     milliseconds, then you should either invoke the "update idletasks"
     Tcl/Tk command periodically within the callback, or you should
     break the callback's calculations up into several separate
     routines that can be invoked by separate events.

  4. Don't leak memory

     Once started, GUI programs tend to run for a long time -- hours,
     days, weeks or even months.  Hence, you should take special care
     to avoid memory leaks.  A memory leak occurs when you allocate a
     chunk of memory from the heap using malloc() but don't return
     that memory to the heap using free() when you are done with it.
     Because the memory was not released by free() it can never be
     reused.  When this happens, the amount of memory required by your
     application will constantly increase, until at some point it will
     consume all memory available, and then die.  Memory leaks are
     probably the most common bug in GUI programs (which is why I
     mention them.)


5.  The Main Body Of An ET Application

The main() routines for ET applications all look pretty much alike.
Here's a template:

  void main(int argc, char **argv){
    Et_Init(&argc,argv);   /* Start the Tcl/Tk interpreter */
    /* Create new Tcl/Tk commands here */
    /* Initialize data structures here */
    /* Create windows for the application here */
    Et_MainLoop();         /* The event loop */
  }

When you need to write an ET application, but you aren't sure where to
begin, this template is a good starting point.  Type in the above
template and make sure you can successfully compile and run it.  (The
program that results from compiling the template creates a blank
window that doesn't respond to any mouse or keyboard inputs.  Its the
equivalent of "wish /dev/null".) After you get the template running,
slowly begin adding bits of code, recompiling and testing as you go,
until you have a complete application.

Let's take a closer look at each line of the template, so that you can
better understand what is going on.

The first line of main() is a call to the Et_Init() procedure.  The
Et_Init() procedure initializes the ET system and the Tcl/Tk
interpreter.  It must be called before any other ET function or
statement.  The parameters are the argc and argv formal parameters of
main().  Et_Init() uses these parameters to look for command-line
options.  ET currently understands four command-line options:

  *  -display designates the X server to use.  The value of this
     option will override your DISPLAY environment variable.
  *  -name changes the application name for the program.  By default,
     the application name is same as the the filename of the
     executable itself.  The application name is used to derive the
     Tcl/Tk interpreter name for use with the Tcl/Tk send command.
     The application name is also used for processing X11 resources,
     and as the default text on the application's title bar.
  *  -geometry changes the starting size and/or position of the
     program.
  *  -sync turns on synchronous mode in the X server.  This makes the
     program run a lot slower, but is sometimes useful when debugging.
     It is very rarely used.

Notice the "&" before the argc parameter to Et_Init().  The number of
command line arguments is passed to Et_Init() by address, not by
value.  This is so Et_Init() can change the value of argc.  Whenever
Et_Init() sees one of the above command-line options, it removes that
option from the option list in argc and argv.  Hence, after Et_Init()
returns, only application-specific command line options remain.

For example, suppose you invoke an ET program like this:

    myapp -quiet -display stego:0 file1.data

The values of argc and argv that will be passed into the Et_Init()
function are:

    argc = 5
    argv = { "myapp", "-quiet", "-display", "stego:0", "file1.data", 0 }

The Et_Init() function will see the -display stego:0 part and act upon
it accordingly.  It will then remove those fields from the argument
list, so that after Et_Init() returns, the values are these:

    argc = 3
    argv = { "myapp", "-quiet", "file1.data", 0 }

In this way, the initialization code that follows Et_Init() never sees
the ET-specific command line arguments.

After the Et_Init() procedure comes the initialization code.
Normally, you begin the initialization by creating and registering all
the new Tcl/Tk commands you will need.  Section 6 describes how this
is done.  Basically it involves replacing the comment in the template
with one or more ET_INSTALL_COMMANDS statements.  Once you've created
the new Tcl/Tk commands, you may need to construct internal C data
structures, or create linkages between C variables and Tcl variables
using Tcl's Tcl_LinkVar() function.  Command-line options that haven't
been removed by Et_Init() are often processed here, as well.  Finally,
you will probably want to create the initial windows for the
application.  The ET() function (see section 7) and ET_INCLUDE()
procedure (see section 8) are both good for this.

Of course, this is only a suggested outline of how to initialize your
application.  You should feel free to do something different if your
program requires it.  The only ground rule is that the initialization
code shouldn't try to interact with the user.  Instead, use callback
routines to respond to user inputs.

The last line of main() is a call to the Et_MainLoop() procedure.
Et_MainLoop() implements the event loop.  It will not return until the
program is ready to exit.


6.  Writing Tcl/Tk Routines In C

One of the first things people tend to do with ET is create new Tcl/Tk
commands, written in C, that do computations that are either too slow
or impossible with a pure Tcl. This is a two-step process.  First you
have to write the C code using the ET_PROC construct.  Then you have
to register your new Tcl/Tk command with the Tcl/Tk interpreter using
the ET_INSTALL_COMMANDS statement.  We will consider each of these
steps in turn.

6.1 The Decimal Clock Example Program

To help illustrate the concepts, this section introduces a new example
program: the decimal clock.  The decimal clock displays the current
time of day as a decimal number of hours.  For instance, 8:30am will
display as "8.500".  11:15pm will show as "23.250".  And so forth.  A
screen shot of this program is shown nearby in figure 6.1.

                              **Image**
             6.1 Typical appearance of the decimal clock

We'll begin by looking at the main procedure for the decimal clock
program.

  void main(int argc, char **argv){
    Et_Init(&argc, argv);
    ET_INSTALL_COMMANDS;
    ET(
      label .x -bd 2 -relief raised -width 7
      pack .x
      proc Update {} {
        .x config -text [DecimalTime]
        after 3600 Update
      }
      Update
    );
    Et_MainLoop();
  }

As you can see, the main procedure is just a copy of the program
template from section 5, with some of the comments replaced by actual
initialization code.  The first initialization action is to invoke the
special ET statement ET_INSTALL_COMMANDS. Don't worry about what this
does just yet -- we'll return to it a little later.  The second
initialization action is a single ET() function containing 7 lines of
Tcl/Tk. This Tcl/Tk code does three things:

  *  It creates a label widget in which to show the decimal time.
  *  It creates a new Tcl/Tk procedure named Update that updates the
     label widget to show the current time, and then arranges to call
     itself again after 0.001 hours (3.6 seconds).
  *  It invokes the Update procedure once in order to initialize the
     text of the label widget, and to start the periodic updates.

Like all well-behaved ET programs, the main procedure for the decimal
clock concludes by entering the event loop.

6.2 The ET_PROC Statement

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 to 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.

  *  argc

     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.

  *  argv

     Like argc before it, this parameter work 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.

  *  interp

     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.)

  *  clientData

     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 of
"SetResult".) If all this seems too complicated, then you can choose
to do nothing at all, in which case the the return value will default
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 #defines 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.

6.3 The ET_INSTALL_COMMANDS statement

We've seen how the ET_PROC constuct will create a new Tcl/Tk command.
But that command must still be registered with the Tcl interpreter
before it can be used.  Fortunately, ET makes this very easy.

ET uses the ET_INSTALL_COMMANDS keyword to register ET_PROC commands
with the Tcl interpreter.  The et2c preprocessor converts the
ET_INSTALL_COMMANDS keyword into a sequence of C instructions that
will register every ET_PROC in the current file.  In the main()
procedure of the decimal clock example, the ET_INSTALL_COMMANDS
keyword that immediately follows the Et_Init() function is used to
register the DecimalTime command.  As it turns out, DecimalTime is the
only ET_PROC function in the same source file, but even if there had
be 100 others, they would have all been registered by that single
ET_INSTALL_COMMANDS statement.

The ET_INSTALL_COMMANDS keyword can also be used to register ET_PROC
functions in separate source files, simply by putting the name of the
source file in parentheses after the ET_INSTALL_COMMANDS keyword.
Like this:

   ET_INSTALL_COMMANDS( otherfile.c );

A larger program will typically have many ET_INSTALL_COMMANDS
statements immediately following the Et_Init() function, one statement
for each file that contains ET_PROC functions.  One recent commercial
project used 33 ET_INSTALL_COMMANDS statements following the Et_Init()
function!

6.4 Summary Of Writing Tcl/Tk Commands In C

Before leaving this section, let's briefly summarize the steps needed
to create new Tcl/Tk commands in C using ET. First you create one or
more commands using the ET_PROC construct, as follows:

  ET_PROC( name-of-the-new-command ){
    /* C code to implement the command */
    return ET_OK;  /* Don't forget the return value! */
  }

Then, you register these commands with the Tcl interpreter using an
ET_INSTALL_COMMANDS statement after the Et_Init() function call within
main().  Like this:

  ET_INSTALL_COMMANDS( name-of-file-containing-ET_PROCs.c );

And that's all you have to do!

The ET_PROC construct gives you the capability of putting a C routine
in the middle of Tcl/Tk. The next section will take a closer look at
ET() which allows you to put Tcl/Tk in the middle of a C routine.


7.  The ET() Function And Its Siblings

If you've been keeping up with the examples, you've already seen the
ET() function used twice to insert a few lines of Tcl/Tk in the middle
of a C procedure.  But the ET() function will do a lot more, as this
section will show.

7.1 Moving Information From Tcl/Tk To C

The first thing to note about ET() is that, just like a real C
function, it has a return value.  ET() returns an integer status code
which is either ET_OK or ET_ERROR depending on whether the enclosed
Tcl/Tk was successful or failed.  (ET() might also return TCL_RETURN,
TCL_BREAK, or TCL_CONTINUE under rare circumstances.)

The status return of ET() is nice, but in practice it turns out to be
mostly useless.  What you really need is the string value returned by
the enclosed Tcl/Tk script.  That's the purpose of the ET_STR()
function.

The ET_STR() function works a lot like ET(). You put in a Tcl/Tk
script as the argument, and the script gets executed.  But instead of
returning a status code, ET_STR() returns a pointer to a string that
was the result of the last Tcl/Tk command in its argument.

The ET_STR() function turns out to be a very handy mechanism for
querying values from Tcl/Tk. For instance, suppose your program has an
entry widget named ".entry" and some piece of C code needs to know the
current contents of the entry.  You can write this:

  char *entryText = ET_STR(.entry get);

Or, imagine that you need to know the current size and position of
your main window.  You might use code like this:

  int width, height, x, y;
  sscanf(ET_STR(wm geometry .),"%dx%d+%d+%d",&width,&height,&x,&y);

Does your C routine need to know the value of a Tcl variable? You
could use the cumbersome Tcl_GetVar() function, but it's much easier
to say:

  char *zCustomerName = ET_STR(set CustomerName);

Possible uses for ET_STR() seem limitless.

But, there are two subtleties with ET_STR() that programmers should
always keep in mind.  The first is that the Tcl/Tk script in the
argument is executed at Tcl's global variable context level.  This
means that all of the Tcl/Tk variables ET_STR() creates, and the only
Tcl/Tk variables it can access, are global variables.  This fact also
applies to the regular ET() function, and to two other function we
haven't talked about yet: ET_INT() and ET_DBL(). ET provides no means
for C code to access or modify local variables.  On the other hand,
this has not proven to be a serious hardship in practice.

The second subtlety with ET_STR() is more dangerous, but fortunately
applies to ET_STR() only.  Recall that ET_STR() returns a pointer to a
string, not the string itself.  The string actually resides in memory
that is held deep within the bowels of Tcl/Tk. The danger is that the
next Tcl/Tk command may choose to change, deallocate, or reuse this
memory, corrupting the value returned by ET_STR(). We say that the
return value of ET_STR() is "ephemeral", meaning that it only lasts
for a brief time.

One way to overcome the ephemerality of ET_STR() is by making a copy
of the returned string.  The strdup() function is good for this.
(Unfortunately, strdup() is missing from a lot of C libraries.  You
may have to write your own string duplicator.) In place of the
examples given above, you might write

  char *entryText = strdup( ET_STR(.entry get) );

or

  char *zCustomerName = strdup( ET_STR(set CustomerName) );

The strdup() function uses malloc() to get the memory it needs, so if
you use this approach, be sure to free() the value when you are done
to avoid a memory leak!

The other way to overcome the ephemerality of ET_STR() is simply not
to use the returned string for very long.  You should be safe in using
the returned string as long as you don't invoke any other Tcl/Tk
commands, or return to the event loop.  Code like this

  sscanf(ET_STR(wm geometry .),"%dx%d+%d+%d",&width,&height,&x,&y);

is OK since we are only need the return value for the duration of the
sscanf() function and sscanf() doesn't use Tcl/Tk.

In addition to ET() and ET_STR(), the ET system provides two other
functions named ET_INT() and ET_DBL(). Both take a Tcl/Tk script for
their argument, as you would expect.  But ET_INT() returns an integer
result and ET_DBL() returns a floating-point value (a double).  In a
sense, these two functions are extensions of ET_STR(). In fact, ET_INT()
does essentially the same thing as

   int v = strtol( ET_STR(...), 0, 0);

and ET_DBL() is equivalent to

   double r = strtod( ET_STR(...), 0);

Because ET_INT() and ET_DBL() return a value, not a pointer, their
results are not ephemeral nor subject to the problems that can come up
with ET_STR().

7.2 Moving Information From C To Tcl/Tk

We've seen how ET_STR(), ET_INT() and ET_DBL() can be used to pass
values from Tcl/Tk back to C. But how do you go the other way and
transfer C variable values into Tcl/Tk? ET has a mechanism to
accomplish this too, of course.

Within the argument to any ET() function (or ET_STR() or ET_INT() or
ET_DBL()), the string "%d(x)" is special.  When ET sees such a string,
it evalutes the integer C expression "x", converts the resulting
integer into decimal, and substitutes the integer's decimal value for
the original string.  For example, suppose you want to initialize the
Tcl/Tk variable named nPayment to be twelve times the value of a C
variable called nYear.  You might write the following code:

    ET( set nPayment %d(12*nYear) );

As another example, suppose you want to draw a circle on the canvas
.cnvs centered at (x,y) with radius r.  You could say:

    id = ET_INT( .cnvs create oval %d(x-r) %d(y-r) %d(x+r) %d(y+r) );

Notice here how the ET_INT() function was used to record the integer
object ID returned by the Tcl/Tk canvas create command.  This allows
us to later delete or modify the circle by referring to its ID. For
example, to change the fill color of the circle, we could execute the
following:

    ET( .cnvs itemconfig %d(id) -fill skyblue );

If you want to substitute a string or floating-point value into an ET()
argument, you can use %s(x) and %f(x) in place of %d(x).  The names of
these substitutions phrases were inspired by the equivalent
substitution tokens in the standard library function printf().  Note,
however, that you cannot specify a field-width, precision or option
flag in ET() like you can in printf().  In other words, you can use
conversions like %-10.3f in prinf() but not in ET(). The ET() function
will only accept an unadorned conversion specification, such as %f.

But the ET() function does support a conversion specifier that
standard printf() does not: the %q(x) substitution.  The %q works like
%s in that it expects its argument to be a null-terminated string, but
unlike %s the %q converter inserts extra backslash characters into the
string in order to escape characters that have special meaning to
Tcl/Tk. Consider an example.

  char *s = "The price is $1.45";
  ET( puts "%q(s)" );

Because %q(s) was used instead of %s(s), an extra backslash is
inserted immediately before the "$".  The command string passed to the
Tcl/Tk interpreter is therefore:

  puts "The price is \$1.45"

This gives the expected result.  Without the extra backslash, Tcl/Tk
would have tried to expand "$1" as a variable, resulting in an error
message like this:

  can't read "1": no such variable

In general, it is always a good idea to use %q(...) instead of %s(...)
around strings that originate from outside the program -- you never
know when such strings may contain a character that needs to be
escaped.

7.3 Summary Of The ET() Function

And that's everything there is to know about the ET() function and its
siblings.  In case you missed something amid all the details, here's a
10 second review of the essential facts:

  *  The ET() executes Tcl/Tk code and returns a success/failure code.
  *  ET_STR(), ET_INT() and ET_DBL() do the same, but return a string,
     and integer or a double which was the result of the last Tcl/Tk
     command executed.
  *  The return value from ET_STR() is ephemeral.
  *  The strings %s(...), %d(...) and %f(...) insert string, integer
     and double C expressions into the argument of ET() and its
     siblings.
  *  The string %q(...) works like %s(...) but adds backslashes before
     characters that are special to Tcl/Tk.

Now lets move on and talk about a similar construct, ET_INCLUDE(),
that allows you incorporate a whole files full of Tcl/Tk into your
application.


8.  Including External Tcl/Tk Scripts In A C Program

In the example programs seen so far in this article, Tcl/Tk code in an
ET() function was used to construct the main window.  This works fine
for the examples, since their windows are uncomplicated and can be
constructed with a few lines code.  But in a real application, or even
a more complex example, the amount of Tcl/Tk code needed to initialize
the program's windows can quickly grow to hundreds or thousands of
lines.  It is impractical and irksome to put this much code into an ET()
statement, so the ET system provides another way to get the job done:
the ET_INCLUDE() statement.

The ET_INCLUDE() statement is similar in concept #include statement in
the C preprocessor.  Both take a filename as their argument, and both
read the named file into the original source program.  The ET_INCLUDE()
statement expects its file to be pure Tcl/Tk code, though.  It's job
it to turn the Tcl/Tk source into a form that the C compiler can
understand, and to arrange for the Tcl/Tk to be executed when control
reaches the ET_INCLUDE() statement.

An example may help to clarify this idea.  In the decimal clock
program (way back at the beginning of section 6), there are 7 lines of
Tcl/Tk in an ET() function used to create the application's main
window.  Now suppose we remove those 7 lines of Tcl/Tk into a separate
file named dclock.tcl.  Then we could replace the ET() function with
an ET_INCLUDE() statement that references the new file.  Like this:

  void main(int argc, char **argv){
    Et_Init(&argc, argv);
    ET_INSTALL_COMMANDS;
    ET_INCLUDE( dclock.tcl );
    Et_MainLoop();
  }

When the et2c preprocessor see the ET_INCLUDE() statement, it locates
the specified file, reads that file into the C program, and makes
arrangements for the text of the file to be executed as if it had all
appeared within an ET() function.

Well, almost like an ET() function.  There are a couple of minor
differences.  The ET_INCLUDE() does not understand the various %s(...)
substitutions as ET() does.  Also, the ET_INCLUDE() is a true
procedure.  It doesn't return a value of like ET() so you can't use an
ET_INCLUDE() in an expression.

It is important to understand the difference between an ET_INCLUDE()
statement like this

    ET_INCLUDE( dclock.tcl );

and the source command of Tcl/Tk, used as follows:

    ET( source dclock.tcl );

The ET_INCLUDE() statement reads the Tcl/Tk into the program at
compile-time, effectively making the Tcl/Tk code part of the
executable.  The Tcl source command, on the other hand, opens and
reads the file at run-time, as the application executes.  This makes
the executable a little smaller, but it also means that the file
containing the Tcl/Tk must be available to the executable whenever it
runs.  If you move just the executable, but not the Tcl/Tk file, to
another computer, or even another directory, then it will no longer
work because it won't be able to locate and read the Tcl/Tk file.

The ability to read an external Tcl/Tk script and make it part of the
executable program is an important feature of ET. But while you are
developing and testing a program, it is sometimes convenient to turn
this feature off and to have the application read its scripts at
run-time instead of compile-time.  That way, you can make changes to
the Tcl/Tk script and rerun your program with the changes, but without
having to recompile.  You can do this using the -dynamic option to the
et2c proprocessor.  Whenever you run et2c with the -dynamic
command-line option, it effective turns instances of the statement

    ET_INCLUDE( filename.tcl );

into the statement

    ET( source filename.tcl );

This feature has proven very helpful during development.  But be
careful to turn it off before doing your final build, or else you
won't be able to move your executable to other machines!

There is just one other feature of the ET_INCLUDE() statement that we
need to discuss before moving on, and that is the algorithm it uses to
locate the Tcl/Tk source code files.  Just like the C preprocessor's
#include statement, the ET_INCLUDE() mechanism has the capability of
including files found in other directories.

The et2c preprocessor always looks first in the working directory for
files named by an ET_INCLUDE() statement.  If the file is found there,
no further search is made.  But if the file is not found, then et2c
will also look in all directories named in -I command line options.
For example, if you run et2c like this:

    et2c -I../tcl -I/usr/local/lib/tcl app.c >app_.c

and the app.c file contains a line of the form:

    ET_INCLUDE( setup.tcl );

then et2c will search for the setup.tcl first in the "." directory,
then in "../tcl" and in "/usr/local/lib/tcl".  It will use the first
instance of setup.tcl that it finds.


9.  Global Variables In ET

The et.o library for ET defines three global C variables that are
sometimes of use to programmers.  In addition, the Et_Init() creates
two new global Tcl/Tk variables that many programs find useful.  This
section will describe what all of these variables do, and suggest ways
that they can be used.

9.1 Global C Variables Created By ET

Perhaps the most useful of the global variables available in ET is
Et_Interp.  This variable is a pointer to the Tcl/Tk interpreter, the
one created by Et_Init() and used to execute all Tcl/Tk commands
within the program.  The Et_Interp variable has the same value as the
interp formal parameter found in every ET_PROC() function.

The Et_Interp variable is useful because you may often want to call C
routines in the Tcl/Tk library, and most of these routines require a
pointer to the interpreter as their first parameter.  For instance,
suppose in the initialization code you want to create a link between
the global C variable nClients and a Tcl/Tk variable by the same name.
Using the Et_Interp variable as the first parameter to the Tcl
function Tcl_LinkVar(), you could write:

   Tcl_LinkVar(Et_Interp,"nClients",(char*)&nClients,TCL_LINK_INT);

Having done this, any changes to the C nClients variable will be
reflected in the Tcl/Tk variable, and vice versa.

Perhaps the second most useful global varible is Et_Display.  This
variable contains the Display pointer required as the first argument
to most Xlib routines.  It is used by daring, down-to-the-bare-metal
programmers who like to call Xlib directly.

Here's an example.  Suppose you want to create a new Tcl/Tk command,
PitchedBell, that makes the X terminal emit a beep with a pitch
specified by its sole argument.  Once such a command is implemented,
then the following Tcl/Tk code would emit a single tone at the pitch
of concert A:

   PitchedBell 440

Here a short piece of Tcl/Tk code that will play the opening bar of
Beethoven's Fifth Symphony:

   foreach pitch {784 784 784 659} {
      PitchedBell $pitch
      after 200
   }

You probably get the idea.  Here's the code that implements the
PitchedBell command:

   #include <tk.h>   /* Will also pickup <Xlib.h> */

   ET_PROC( PitchedBell ){
      XKeyboardControl ctrl;   /* For changing the bell pitch */

      if( argc!=2 ){
        interp->result = 
          "Wrong # args.  Should be: ``PitchedBell PITCH''";
        return ET_ERROR;
      }
      ctrl.bell_pitch = atoi( argv[1] );
      XChangeKeyboardControl(Et_Display,KBBellPitch,&ctrl);
      XBell(Et_Display,0);
      XFlush(Et_Display);
      return ET_OK;
   }

After checking to make sure it has exactly one argument, the
PitchedBell command first uses the XChangeKeyboardControl() function
of Xlib to change the bell pitch.  It then rings the bell using the
XBell() Xlib function, and finally flushes the Xlib message queue
using XFlush() to force the bell to be rung immediately.  All three of
these Xlib functions require a Display pointer as their first
argument, a role that is perfectly filled by the Et_Display global
variable.

The third and final global C variable in ET is Et_MainWindow.  This
variable is a pointer the Tcl/Tk structure that defines the
applications main window.  Back in the days of Tk3.6, there were
several Tcl/Tk library functions that required this value as a
parameter.  But the Tcl/Tk library interface changed in the move to
Tk4.0, so that the main window pointer is no longer required.  Hence,
the Et_MainWindow variable isn't used much any more.  It has been kept
around as an historical artifact.

9.2 Tcl/Tk Variables Created By ET

Besides the 3 global C variables, ET also provides two Tcl/Tk
variables that are of frequent use: cmd_name and cmd_dir.  The
cmd_name contains the name of the file holding the executable for the
application, and cmd_dir is the name of the directory containing that
file.

The cmd_name and cmd_dir variables are useful to programs that need to
read or write auxillary data files.  In order to open an auxillary
file, the program needs to know the files pathname, but it is not a
good idea to hard-code a complete pathname into the program.
Otherwise, the auxillary file can't be moved without recompiling the
program.  By careful use of cmd_name and/or cmd_dir, we can arrange to
have auxillary files located in a directory relative to the
executable, rather that at some fixed location.  That way, a system
adminstrator is free to move the auxillary file to a different
directory as long as the executable moves with it.

For example, suppose you are writing a program named acctrec that
needs to access a data file named acctrec.db. Furthermore, suppose the
data file is located in a directory ../data relative to the
executable.  Then to open the data file for reading, a program could
write:

  char *fullName = ET_STR( return $cmd_dir/../data/$cmd_name.db );
  FILE *fp = fopen(fullName,"r");

Using this scheme, both the executable and the datafile can be placed
anywhere in the filesystem, as long as they are in the same position
relative to one another.  They can also be renamed, so long as they
retain the same base name.  This flexibility is a boon to system
adminstraters, and also make the program less sensitive to
installation errors.


10.  Reading From Standard Input

There's one last feature of ET that we haven't discussed: the
Et_ReadStdin() procedure.  If this procedure is called (with no
arguments) in between the calls to Et_Init() and Et_MainLoop(), then
ET will make arrangements to read all data that appears on standard
input and interpret that data as Tcl/Tk commands.

You can use the Et_ReadStdin() to implement the interactive wish
interpreter for Tcl/Tk. The code would look like this:

  main(int argc, char **argv){
    Et_Init(&argc,argv);
    Et_ReadStdin();
    Et_MainLoop();
  }

Let's call this program etwish in order to distinguish it from the
standard wish that comes with Tcl/tk.  The etwish program differs from
wish in two ways.  First, wish reads a set of 15 or so Tcl/Tk scripts
from a well-known directory when it first starts up.  Thus, to install
wish, you have to have both the wish executable and the 15 startup
scripts.  But with etwish, the 15 startup scripts are compiled into
the executable (using ET_INCLUDE() statements inside the Et_Init()
function) so the external scripts are no longer required.  This does
make the etwish executable slightly larger (by about 64K bytes), but
it also makes the program much easier to install and administer.

The second difference between wish and the etwish program shown above
is that etwish is always interactive.  It will not read a script from
a file given as a command line argument like standard wish will.  But
we can remove that difference using a little more code.

  main(int argc, char **argv){
    Et_Init(&argc,argv);
    if( argc>2 && (strcmp(argv[1],"-f")==0 || strcmp(argv[1],"-file")==0) ){
      ET( source "%q(argv[2])" );
    }else if( argc>1 ){
      ET( source "%q(argv[1])" );
    }else{
      Et_ReadStdin();
    }
    Et_MainLoop();
  }

This revised program serves as a great template for building
customized editions of wish that have one or more new Tcl/Tk commands
written in C. All you have to do is code the new commands using the
ET_PROC() mechanism and insert a single ET_INSTALL_COMMANDS statement
right after the Et_Init().


11.  Compiling ET Applications

We've already discussed the basics of compiling ET applications back
in section 2 when we put together the "Hello, World!" example.
Basically, all you do is preprocess your source files with et2c then
run the results through the C compiler.  But that synopsis omits a lot
of detail.  This section aims to fill in the missing information.

11.1 Compiling ET Itself

But before we begin talking about how to compile ET applications, we
need to first mention how to compile ET itself -- the et2c
preprocessor and the et.o library.

The source code to the et2c preprocessor is contained in a single file
named et2c.c. The preprocessor is written in highly portable K&R C and
should compile without change on just about any 32-bit architecture.
All you have to do is this:

  cc -O -o et2c et2c.c

Compiling the et.o library is a little more problematic, but is still
not difficult.  There are three steps.  First you have to select an
appropriate source code file.  There are different versions of the
source code (sometimes radically different) depending on which version
of Tcl/Tk you are using.  For Tk version 3.6, choose et36.c. For Tk
version 4.0, choose et40.c. For Tk version 4.1 on UNIX and X11, choose
et41.c. Your ET distribution may also have other options, such as
versions for MS-Windows or Macintosh, or versions with built-in
support for various Tcl extensions.

Let's suppose, for the sake of discussion, that you selected the
source file et41.c. The next step is to preprocess this file using
et2c.  This step is a little tricky because we have to use the "-I"
option to et2c to tell the preprocessor where to find the Tcl/Tk
startup scripts.

Recall that the stardard Tcl/Tk interpreter program, wish, reads and
executes a series of Tcl/Tk scripts when it first starts up.  These
scripts set up default widget bindings, create procedures for handling
menus, and so forth.  The names of the directories from which these
scripts are loaded are hard-coded in the wish executable.  There are
about 15 different startup scripts (the number varies from one version
of Tcl/Tk to the next) and wish will not run without them.

But ET applications doesn't read the startup scripts at run-time.
Instead, a series of ET_INCLUDE() statements inside the Et_Init()
function bind the startup scripts into an ET executable at
compile-time.  This feature is what enables ET applications to run on
machines that do not have Tcl/Tk installed.

It is because of 15 or so startup scripts included by ET_INCLUDE()
statements in the ET library that we have to preprocess the library
source code using et2c.  But we also have to tell et2c what
directories to use when searching for the startup scripts.  If Tcl/Tk
has already been installed on your system, then you can find out the
names of the startup script directories by executing the following
wish script:

  #! wish
  puts $tk_library
  puts $tcl_library

Let's suppose that the startup scripts are located in the directories
/usr/local/lib/tcl and /usr/local/lib/tk.  Then the command to
preprocess the ET library source code would be the following:

  et2c -I/usr/local/lib/tcl -I/usr/local/lib/tk et41.c >et.c

After preprocessing the library source code, all that remains is to
compile it.  The library references the <tk.h> header file, which in
turn references <tcl.h>, so you may have to add some -I options to the
compiler command line to specify the directories where these header
files are located.  The following is typical:

  cc -c -o et.o -I/usr/include/tcl -I/usr/include/tk et.c

11.2 Compiling The Application Code

Once you get et2c and et.o compiled, the hard work is done.  To build
your application, simply run each source file through the et2c
preprocessor before compiling it, and add the et.o library with the
final link.  For example, the steps to compile a program from two
source files, appmain.c and appaux.c, will be the something like
following, on most systems:

   et2c appmain.c >temp.c
   cc -c temp.c -o appmain.o
   et2c appaux.c >temp.c
   cc -c temp.c -o appaux.o
   cc appmain.o appaux.o et.o -ltk -ltcl -lX11 -lm

If you're using a Makefile, you might want to redefine the default
rule for converting C source code into object code to incorporate the
et2c preprocessor step.  Like this:

    .c.o:
          et2c $temp.c
          cc -c -o $@ temp.c

The et2c does not harm to files that don't use ET constructs, so this
rule will work for every file in your project.

11.3 Turning Off Script Compression In The Preprocessor

The et2c preprocessor attempts to save memory and improve performance
of your application by removing comments and unnecessary spaces from
the Tcl/Tk code inside ET() functions and loaded by ET_INCLUDE()
statements.  This mechanism works well, most of the time, but it is
not foolproof.  It is theoretically possible for a valid Tcl/Tk script
to be corrupted by et2c's compression attempts.  If you experience
trouble, and suspect that et2c is messing up your Tcl/Tk code, then
you can turn script compression off using the -nocompress command line
option.

11.4 Compiling Using An Older K&R Compiler

If it is your misfortune not to have an ANSI C compiler, you can still
use ET. The source code to et2c is pure K&R C and should work fine
under older compilers.  The source code to et.o is another matter.  To
compile the library using an older compiler you will need to first
give a -K+R option to et2c and then give a -DK_AND_R option to the C
compiler.  Like this:

  et2c -K+R -I/usr/local/lib/tcl -I/usr/local/lib/tk et40.c >et.c
  cc -DK_AND_R -I/usr/include/tcl -I/usr/include/tk -c et.c

When compiling application code using an older compiler, just give the
-K+R option to et2c.  It is not necessary to give the -DK_AND_R option
to the C compiler when compiling objects other than et.c.


12.  Other ET Example Programs

Besides the very simple "Hello, World!" and decimal clock programs
presented above, ET is distributed with a number of non-trivial
example programs.  This section will briefly overview what several of
these example programs do, and why ET was important to their
implementation.  We won't try to explain the details of how the
programs work, though.  You can figure that out for yourself by
looking at the source code.

12.1 The Color Chooser

There is a color chooser tool for X11 called color.  The sources to
color are in the files color.c and color.tcl.  A screen image of the
program is shown in figure 12.1.

                              **Image**
             12.1 Typical appearance of the color program

The X11 Windowing System supports displays with over 280 quadrillion
distinct colors (48 bits per pixel).  But from this vast number, a few
hundred colors are assigned English names like "blue" or "turquoise"
or "peachpuff".  All the rest are given arcane hexadecimal
designations like "#b22cd8517f32".  It is best to use colors with
English names whenever possible.

The purpose of the color program it to help select colors with English
names.  At the top of the application is large swatch showing one of
the 280 quadrillion X11 colors, together with either its English name
(if it has one) or its hexadecimal value.  Sliders on the lower left
side of the window allow the user to vary the color of the swatch by
changing various color components.  On the lower right side of the
window are six smaller swatches that show colors with English names
that are similar to the color in the main swatch.  Moving any of the
six color component sliders causes the colors in all swatches, and the
other sliders, to update in real time.  Clicking on any of the smaller
swatches transfers it color to the main swatch, updating all of the
sliders and swatches appropriately.

In theory, there is nothing to prevent the color program from being
coded in pure Tcl/Tk, but in practice, such an implementation would be
much too slow.  For this reason, two key routines are coded in C. The
ET_PROC command ChangeComponent is called whenever one of the color
component sliders is moved.  This routine moves the other sliders,
changes the color of the main swatch, then computes close colors for
the smaller swatches.  Another ET_PROC command named ChangeColor is
called whenever the user clicks on one of the smaller swatches.  This
routine changes the color of the main swatch, then updates the sliders
and the smaller swatches accordingly.

12.2 The VT100 Terminal Emulator

The example named tkterm implements a VT100 terminal emulator.  The
tkterm program can be used as a direct replacement for the more
familiar emulator programs xterm or rxvt.

The sources for tkterm are contained in three separate files.  The
main procedure is in tkterm.c. Tcl/Tk for constructing the main window
for the application is in tkterm.tcl.  Finally, the file getpty.c
takes care of the messy details of allocating a pseudo-TTY for the
emulator and invoking a shell in the pseudo-TTY. (Much of the code in
getpty.c was copied from rxvt.)

The tkterm program simulates the VT100 display using an ordinary
Tcl/Tk text widget.  C routines in tkterm.c interpret the characters
and escape sequences coming into the program and uses ET() functions
to insert characters into their proper places within the text widget.
The tkterm.c file is almost 1000 lines long, and is mostly devoted to
interpreting the VT100 escape codes.

The tkterm program is an example of an application that could not be
coded in pure Tcl/Tk, since Tcl/Tk has no provisions for dealing with
pseudo-TTYs or TTYs in "raw" mode.  But even if it could, we would
probably still want to use some C code, since it seems unlikely that a
Tcl/Tk script would be able to process the VT100 escape sequences
efficiently.

12.3 A Real-Time Performance Monitor For Linux

The perfmon program is a system performance monitor for the Linux
operating system.  It uses bar graphs to shows the amount of memory,
swap space, currently being used.  The display is updated 10 times per
second.  There are two source code files for this application:
perfmon.c and perfmon.tcl.

The main display of the perfmon program is implemented using a Tcl/Tk
canvas widget.  But for efficiencies sake, the logic that computes the
current memory, swap space, and CPU usages is all coded in C. The C
code obtains the system performance data by reading the files
/proc/stat and /proc/meminfo.  It then processes this information into
the desired preformance measurements and make appropriate changes to
the Tcl/Tk bar graphs using ET() function calls.

On a 90MHz Pentium and with an update frequency of 10 times per
second, the prefmon program uses a negligible amount of the CPU time.
So in addition to being a nifty desktop utility for a Linux
workstation, this example demonstrates that Tcl/Tk applications can be
very efficient.

12.4 An ASCII Text Editor And A File Browser

The two programs tkedit and browser implement, respectively, an ASCII
text editor and a UNIX file browser utility.  Source code to these
programs is in the files tkedit.c, tkedit.tcl, browser.c and
browser.tcl.

Both of these programs could just as well have been implemented as
pure Tcl/Tk scripts, with no loss of features or performance.  (In
fact, the browser can be used as pure script by invoking the
browser.tcl using wish.) But, sometimes you want a program to be a
real executable, not a script.  For instance, you may want to be able
to run the program on machines that do not have Tcl/Tk installed.  Or,
perhaps you want the programs to run on machines that have a
different, incompatible version of Tcl/Tk installed.

The tkedit and browser programs are examples of how to convert a pure
Tcl/Tk script into a stand-alone program using ET. The idea is very
simple.  Your C code simply initializes ET, invokes your script using
a single ET_INCLUDE() statement, and then enters the event loop.  Like
this:

  void main(int argc, char **argv){
    Et_Init(&argc,argv);
    ET_INCLUDE( browser.tcl );
    Et_MainLoop();
  }

Compiling this code results in a stand-alone application that can be
run on any binary-compatible machine.


13.  Using ET To Build MS-Windows And Macintosh Applications

ET, like Tcl/Tk, was originally written to support the open X11
windowing system only.  But nowadays, people often need to write
applications for popular proprietary windowing systems such as
Windows-95 or Macintosh.  Beginning with release 4.1, Tcl/Tk will
support these proprietary products, and ET support will follow soon
thereafter.

The exact details of how to build ET applications for Macintosh and
MS-Windows are still sketchy, since implementations for these
platforms have not yet been completed, but a general idea has emerged.

On a Macintosh, ET applications that don't call Xlib directly should
compile with little or no changes.  The Mac won't support the
Et_ReadStdin() routine, or the Et_Display global variable, but then
again, neither of these make much sense on a Mac. The application will
compile in much the same way as it is for X11, except that you should
use the et41mac.c source file to the et.o library.

More change is required to support MS-Windows, however.  The Windows
version of ET will not support Et_Init() or Et_MainLoop().  Instead
these functions will be invoked automatically.  An ET program for
Windows should contain a single Et_Main() procedure definition to do
all its setup, and nothing more.  Hence, if you application used to
look like this:

  void main(int argc, char **argv){
    Et_Init(&argc,argv);
    /* Your setup code here */
    Et_MainLoop();
  }

then under Windows, it will look like this instead:

  void Et_Main(int argc, char **argv){
    /* Your setup code here */
  }

Besides that, and the obvious fact that Et_Display is not support, a
Windows ET application should work just like an X11 ET application.
It is compiled in the same way, except that you should use the
et41win.c source file for the et.o library.


14.  Summary And Acknowledgements

Over the past two years, many people have used ET to build programs
from a mixture of Tcl/Tk and C. Projects have ranged in size from
student programming assignments up to large-scale (100000+ lines)
commercial development efforts.  In all cases, ET has proven to be an
effective alternative to other GUI toolkits.

The original implementation of ET grew out of a programming contract
from Lucent Technologies (formerly AT&T Bell Laboratories).  Lucent
Technologies was in turn funded under a contract from the United
States Navy.  Many thanks go to Richard Blanchard at Lucent
Technologies and to Charlie Roop, Dave Toms and Clair Guthrie at
PMO-428 for allowing ET to be released to the public domain.


15.  Author's Name and Address

D. Richard Hipp, Ph.D.
Hipp, Wyrick & Company, Inc.
6200 Maple Cove Lane
Charlotte, NC 28269
704-948-4565
drh@vnet.net
