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:
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 directly. 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 script.
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.
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 .
, and so forth.
The last line is a call to another procedure Hello, World!
button.
Because of 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_.cThe
#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 -lmNotice that you must link the program with ET's
And that's all there is too it!
If you're restless to start programming and are the type of person who prefers to learn at the keyboard rather than from a book, this section is for you. It contains a terse overview of the features of ET. Peruse this section, glance quickly at the examples, 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
Among the support routines in 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 ET_INSTALL_COMMANDS
and ET_PROC
and
some special new functions like
The ET_OK
or ET_ERROR
depending upon whether the script suceeded or failed.
Similar routines
Wherever the string %d(x)
occurs inside an
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)
substitutes
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 ET_PROC
macros, the commands associated with
those macros are registered with the Tcl/Tk interpreter by
invoking ET_INSTALL_COMMANDS(aux.c)
after
the
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
Finally, at the top of its output files,
the #define
s that
make ET_OK
and ET_ERROR
equivalent to
TCL_OK
and TCL_ERROR
. This often
eliminates the need
to put
at the beginning of files that use ET.
#include <tcl.h>
And that's everything in ET! All the rest is just detail.
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 an 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 .
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:
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 happen only in the event loop.
Everything that a GUI program does is 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.
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, 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.
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
The
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
.) After you get the template running, slowly begin adding bits of code, recompiling and testing as you go, until you have a complete application.wish /dev/null
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 argc
and argv
formal parameters of
send
command.
The application name is also used for processing X11 resources,
and as the default text on the application's title bar.
Notice the
before the &
argc
parameter to argc
.
Whenever argc
and argv
.
Hence, after
For example, suppose you invoke an ET program like this:
myapp -quiet -display stego:0 file1.dataThe values of
argc
and argv
passed into the argc = 5 argv = { "myapp", "-quiet", "-display", "stego:0", "file1.data", 0 }The
argc = 3 argv = { "myapp", "-quiet", "file1.data", 0 }In this way, the initialization code that follows
After the 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
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
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.
To help illustrate the concepts, this section introduces a
new sample program: the decimal clock.
The decimal clock displays the current time of day as a decimal
number of hours. For instance,
8:30am displays as
.
11:15pm shows as 8.500
.
And so forth.
A screen shot of this program is shown
in figure .
23.250
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 , 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
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).
Update
procedure once in order to
initialize the text of the label widget, and to start the
periodic updates.
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 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
Like argc
before it, this parameter works just like the
argv
parameter to argv[0]
contains the name of the the
command itself (
in this
example), DecimalTime
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
.
The Tcl_Interp*
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
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.
void*
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:
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 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.
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 ET_INSTALL_COMMANDS
keyword into a sequence of C
instructions that register every ET_PROC
in the current file.
In the ET_INSTALL_COMMANDS
keyword that immediately follows 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_PROC
functions.
One recent commercial project used 33
ET_INSTALL_COMMANDS
statements following the
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_INSTALL_COMMANDS( name-of-file-containing-ET_PROCs.c );And that's all you have to do!
The ET_PROC
construct lets you
put a C routine in the middle of Tcl/Tk.
The next section will take a closer look at
If you've been keeping up with the examples, you've already seen
the
The first thing to note about ET_OK
or ET_ERROR
depending on whether the
enclosed Tcl/Tk was successful or failed.
(TCL_RETURN
, TCL_BREAK
,
or TCL_CONTINUE
under rare circumstances.)
The status return of
The
The
and some piece of C code needs
to know the current contents of the entry.
You can write this:
.entry
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
char *zCustomerName = ET_STR(set CustomerName);Possible uses for
But, there are two subtleties with
The second subtlety with ephemeral.
One way to overcome the ephemerality of
char *entryText = strdup( ET_STR(.entry get) );or
char *zCustomerName = strdup( ET_STR(set CustomerName) );The
The other way to overcome the ephemerality of
sscanf(ET_STR(wm geometry .),"%dx%d+%d+%d",&width,&height,&x,&y);is OK since we need the return value only for the duration of the
In addition to double
).
In a sense, these two functions are extensions of
int v = strtol( ET_STR(...), 0, 0);and
double r = strtod( ET_STR(...), 0);Because
We've seen how
Within the argument to any
is special.
When ET sees such a string, it evalutes the integer C expression
%d(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 x
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
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 %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 C library
function %-10.3f
in %f
.
But 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 variableIn 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.
And that's everything there is to know about the
%s(...)
, %d(...)
and
%f(...)
insert string, integer and double C expressions
into the argument of %q(...)
works like %s(...)
but adds backslashes before characters that are special to Tcl/Tk.
In the sample programs seen so far in this article,
Tcl/Tk code in an
The #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
An example may help to clarify this idea.
In the decimal clock program (way back
at the beginning of section ),
there are 7 lines of Tcl/Tk in an
void main(int argc, char **argv){
Et_Init(&argc, argv);
ET_INSTALL_COMMANDS;
ET_INCLUDE( dclock.tcl );
Et_MainLoop();
}
When the
Well, almost like an %s(...)
substitutions as
It is important to understand the difference between an
ET_INCLUDE( dclock.tcl );and the
source
command of Tcl/Tk, used as follows:
ET( source dclock.tcl );The
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
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 #include
statement, the
The
et2c -I../tcl -I/usr/local/lib/tcl app.c >app_.cand the
ET_INCLUDE( setup.tcl );then
directory, then in.
The
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_Interp
variable has the same value as the
interp
formal parameter found in every
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(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 440Here a short piece of Tcl/Tk code that plays 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 uses the
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 to
the Tcl/Tk structure that defines the application's
main window.
Back in the days of Tk3.6, several Tcl/Tk library functions
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.
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
variable 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 auxiliary data files.
In order to open an auxiliary 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 auxiliary file can't be moved without recompiling
the program.
By careful use of cmd_name
and/or cmd_dir
,
we can arrange to have auxiliary 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 auxiliary file
to a different directory as long as the executable moves with it.
For example, suppose you are writing a program named
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.
There's one last feature of ET that we haven't discussed:
the
You can use the
main(int argc, char **argv){ Et_Init(&argc,argv); Et_ReadStdin(); Et_MainLoop(); }Let's call this program
The second difference between
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
ET_INSTALL_COMMANDS
statement right after the
We've already discussed the basics of compiling ET applications
back in section when we put together the
Hello, World!
example.
Basically, all you do is preprocess your source files with
But before we begin talking about how to compile ET applications,
we need to first mention how to compile ET itself --
the
The source code to the
cc -O -o et2c et2c.c
Compiling the
Let's suppose, for the sake of discussion, that you selected the
source file
Recall that the stardard Tcl/Tk interpreter program,
But ET applications don't read the startup scripts at run-time.
Instead, a series of
It is because of 15 or so startup scripts included by
#! wish puts $tk_library puts $tcl_libraryLet's suppose that the startup scripts are located in the directories
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
cc -c -o et.o -I/usr/include/tcl -I/usr/include/tk et.c
Once you get
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
.c.o: et2c $< >temp.c cc -c -o $@ temp.cThe
The
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 -K+R
option to et2c
and then give
a -DK_AND_R
option to the C compiler:
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.cWhen compiling application code with 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
.
Besides the very simple Hello, World!
and
decimal clock programs presented above, ET is distributed
with a number of non-trivial sample 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.
There is a color chooser tool for X11 called
The X11 Window 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
In theory, there is nothing to prevent 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.
The example named
The sources for
The
The 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.
The
The main display of the
On a 90MHz Pentium and with an update frequency of 10 times per
second, the
The two programs
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
The
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.
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 Windows95 or Macintosh. Beginning with release 4.1, Tcl/Tk supports these proprietary products, and so does ET. (Actually, only Windows95 is supported as of this writing. The author has no access to a Macintosh system on which to develop and test a Macintosh port.)
On a Macintosh, ET applications that don't call Xlib directly should
compile with little or no change.
The Mac won't support 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 does for X11,
except that you should use the
More change is required to support Windows95, however.
The Windows version of ET doesn't contain either
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 supported, 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
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 (100,000+ 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.
The author can be reached at:
D. Richard Hipp
Hipp, Wyrick & Company, Inc.
6200 Maple Cove Lane
Charlotte, NC 28269
704-948-4565
drh@vnet.net