How to Write & Compile a Tcl Application as a Standalone C Program

Building a fully-compiled, standalone C program from Tcl/Tk and C code involves 3 basic steps:

  1. Converting Tcl scripts into C strings (stored as character arrays and .c files);
  2. Calling Tcl_VarEval() for each of these C strings;
  3. Modifying the Tcl/Tk standard tkAppInit.c file.
To eliminate the need for users to install Tcl/Tk on their systems, a fully-compiled, standalone C program must be built as above with two additions:
  1. Perform steps 1 and 2 above on the following "standard" .tcl files: init.tcl, tk.tcl, bgerror.tcl, button.tcl, entry.tcl, focus.tcl, listbox.tcl... (These scripts are part of the Tcl/Tk installation. If you define a widget with your own .tcl script, then exclude the standard script.)

  2. Link the application with the static Tcl/Tk libraries; (e.g., use libtcl7.5.1.a and libtk4.1.a for Tcl version 7.5 and Tk version 4.1.)

The specifics given below are based on the source code and makefiles for sdr and confcntlr-v0.4. The .tcl files used in the explanation below are: uimain.tcl, ctrlmenu.tcl (from confcntlr-v0.4 source code), and libs.tcl (created by the Makefile).

  1. Get a copy of Ron Frederick's "NetVideo Version 3.2 tcl2c.c"--this is available with the sdr and confcntlr source code.
  2. Run all your Tcl files through tcl2c and give them a name such as "tcl_xxx"--e.g.,
    tcl2c tcl_uimain < uimain.tcl > tcl_uimain.c
    tcl2c tcl_ctrlmenu < ctrlmenu.tcl > tcl_ctrlmenu.c
  3. Concatenate the standard .tcl files you will need, delete lines that source other .tcl scripts and perform the previous step.
In the makefile for confcntlr, the commands for these steps are:
UI_FILES = tcl_libs.o tcl_uimain.o tcl_ctrlmenu.o

TK_LIBRARY_FILES = $(LIBRARY_TCL)/init.tcl \
     $(LIBRARY_TK)/tk.tcl \
     $(LIBRARY_TK)/bgerror.tcl \
     $(LIBRARY_TK)/button.tcl \
     $(LIBRARY_TK)/entry.tcl \
     $(LIBRARY_TK)/focus.tcl \
     $(LIBRARY_TK)/listbox.tcl \
     $(LIBRARY_TK)/menu.tcl \
     $(LIBRARY_TK)/palette.tcl \
     $(LIBRARY_TK)/scale.tcl \
     $(LIBRARY_TK)/tearoff.tcl \
     $(LIBRARY_TK)/text.tcl \
     $(LIBRARY_TK)/optMenu.tcl $(LIBRARY_TK)/scrlbar.tcl

(I omitted ($LIBRARY_TK)/dialog.tcl because I wrote my own procedure.)

# Make a .c file from a .tcl file: use the .tcl file being processed
# ($<) as input to tcl2c but rename it so that the filename without
# the extension is used with "tcl_" prepended (tcl_$*)

tcl_%.c: %.tcl
rm -f $@; ./tcl2c tcl_$* < $< > $@
# Create the object files.
tcl_%.o: tcl_%.c
$(CC) -c $<
# Create the concatenated list of standard .tcl files and remove # lines with "source"; redirect the output to "libs.tcl".
libs.tcl: $(TK_LIBRARY_FILES)
cat $(TK_LIBRARY_FILES) | sed '/^[ ]*source[ ]/d' > libs.tcl


Copy the tkAppInit.c file that is included with the Tcl/Tk package into the directory with your source code and make the following modifications. (See mytkAppInit.c in confcntlr's source code for version 0.4.)

-- Declare global character arrays for each of the .c files created from .tcl files:

extern const char tcl_libs[], tcl_uimain[], tcl_ctrlmenu[];
-- Declare global variables for the main window and Tcl interpreter:
static Tk_Window mainWin;
Tcl_Interp *interp;
-- Inside main():
delete the call: Tk_Main();
add this code:
#ifdef WIN32
    extern int TkPlatformInit(Tcl_Interp *interp);
    TkSetPlatformInit(TkPlatformInit);
#endif

add this code:
interp = Tcl_CreateInterp();
Tcl_AppInit(interp);
Tk_MainLoop();
-- Inside Tcl_AppInit():
delete the call to Tcl_Init;
KEEP THE CALL to Tk_Init()!
If not needed, delete the call to Tcl_StaticPackage();

add the following code:

mainWin = Tk_MainWindow(interp);
#ifdef WIN32
{
    extern int WinPutsCmd(ClientData, Tcl_Interp*, int, char **);
    Tcl_CreateCommand(interp,"puts",WinPutsCmd,0,0);
}
#endif

Add the Tcl_CreateCommand() statements for any Tcl commands that your C code will call.

Add: Tcl_VarEval(interp, tcl_libs, 0);
This will evaluate the TK_LIBRARY_FILES. Do NOT use Tcl_Eval().

Add: Tcl_Eval() or Tcl_VarEval() statements for each of the C strings created for your own Tcl scripts; e.g., Tcl_VarEval(interp, tcl_uimain, 0); Tcl_VarEval(interp, tcl_ctrlmenu, 0);

Copy the tkUnixInit.c file from the Tcl/Tk installation (or the file in the sdr or confcntlr source code). This has the TkPlatformInit() function. And, of course, include this file in your source code.