Init Scripts In The DLL

Suppose the Tcl initialization scripts (init.tcl, package.tcl, etc) where compiled into the C library as strings. Consider the advantages of this approach over storing the scripts on the disk of the host computer or appending the initialization scripts to the executable as a ZIP archive or MetaKit database.

  • No more /usr/lib/tcl8.4 directories to create or maintain.
  • Tcl_Init() will never again fail because it was unable to find a compatible init.tcl.
  • The problem of initializing the encodings is solved because ascii.enc is now a static string in the DLL.
  • Security-conscious Tcl programs can no longer be subverted simply by changing the TCL_LIBRARY environment variable.
  • Simplified administration - a version of Tcl becomes just two files, libtcl.dll and tclsh.exe, not those two files plus 98 other initialization scripts. Two files are easier to manage than 100.
  • It becomes much easier to write C/C++ programs that embed a Tcl or Tcl/Tk interpreter.
  • Many starkits can be reduced to just a single DLL.

I (D. Richard Hipp) am especially attracted to the penultimate bullet. For the past decade, I have used Tcl/Tk more as a C library than as a programming language. For much of that time, the C library approach to using Tcl/Tk was scorned, though recently there is growing interest in generating standalone Tcl/Tk executables. The starkit and tclkit concept is a great step in this direction, but it is not quite the same as building the initialization scripts into the library. Note that even if initialization scripts were built into the library, tclkit-like tools would still often be needed to attach application scripts to the executable in order to make it truely standalone.

In some cases, compiling scripts into a library would be able to replace a starkit. If extensions compiled their initialization scripts and resource files into their libraries, then an extension would become just the library. For example, the TkTable extension could be packaged and distributed as tktable.dll instead of tktable.kit. This makes little practical difference to the program, but it might make a huge psychological difference to the user. People know what a DLL is and are comfortable with the idea even if they are not familiar with the low-level details. But one can easily imagine people questioning a KIT - not knowing what it is and hence being reluctant to put it on their systems.

Simplified Embedding Of Tcl Interpreters

The main advantage to putting initialization scripts into the library in my eyes is that it makes Tcl and Tcl/Tk interpreters much easier to embed. How many more people would begin using Tcl or Tcl/Tk in their C programs if it were genuinely easy to compile? It's already pretty simple, but there is still the hassle of making sure the initialization scripts are correctly loaded. But with no initialization worries would could just write:

  int main(int argc, char **argv){
    Tcl_Interp *interp = Tcl_CreateInterp();
    Tcl_Init(interp);
    /* Application specific setup */
    Tcl_Eval(interp, "tcl::command_line_input_loop");
    return 0;
  }

If it were really that simple, how many people would use this approach rather than the 100 or more lines of code needed to embed Python? How many people have been scared away from embedding Tcl by the 178 lines of tclAppInit.c?

The tcl::command_line_input_loop script that is executed in the example above does not (currently) exist in the Tcl script library, as far as I am aware. But it is easy enough to add. All it is this:

   proc command_line_input_loop {} {
     set cmd {}
     while {![eof stdin]} {
       if {[string length $cmd]==0} {set prompt "..."} {set prompt "-->"}
       puts -nonewline $prompt
       append cmd [gets stdin]\n
       if {[info complete $cmd]} {
         if {[catch {uplevel #0 $cmd} result]} {
           puts stderr "ERROR: $result"
         } elseif {[string length $result]>0} {
           puts $result
         }
         set cmd {}
       }
     }
   }

Notice in the example above that we do not check the return value from Tcl_Init(). This is because Tcl_Init() can now no longer fail. Tcl_Init() used to fail if it could not locate a suitable init.tcl script, but since that script is compiled in, failure is no longer an option.

Simplified Embedding Of Tk Interpreters

Embedding Tcl interpreters is easy when initialization scripts are compiled into the library, but on the other hand it was never really that hard. The programmer can omit the call to Tcl_Init() and a Tcl interpreter will continue to work just fine. You won't have a few useful procedures (ex: parray) or alternative encodings, but for many simple applications those things are needed anyway.

But the initialization scripts are much more important for Tk. They contain bindings for the various widgets and the widgets will not work correctly if the initialization scripts are not run first. So putting the initialization scripts into the library is much more helpful to programs that embed Tk. Typical code looks like this:

  int main(int argc, char **argv){
    Tcl_Interp *interp = Tcl_CreateInterp();
    Tcl_Init(interp);
    if( Tk_Init(interp) ){
      fprintf(stderr,"Failed to initialize Tk: %s\n",
        Tcl_GetStringResult(interp));
      exit(1);
    }
    /* Application specific setup */
    Tcl_Eval(interp, "tk::event_loop");
    return 0;
  }

The tk::event_loop procedure would look something like the following:

  bind . <Destroy> {+if {"%W"==""} exit}
  while 1 {vwait forever}

Surely such a simple "main" function would be able to win converts to Tcl from gtk, wxWindows, motif, MFC, and similar GUI libraries.

Some may argue that the main function for a TclKit is already nearly this simple. (I've never looked at the code so I can't be sure. Certainly the main function for TOBE is not much more complex.) But with a TclKit you still have the unfamilar post-linking step of attaching the initialization scripts to the executable using ZIP or MetaKit. When the init scripts are built-in, no post-linking step is required and the makefile is greatly simplified. Furthermore, the common error of attaching in the wrong version of the init scripts is avoided. (Using incompatible libraries and init scripts is the single most common problem experienced by new users of mktclapp.)

Size Of Initialization Scripts

The concern has been raised that some compilers cannot compile long array constants or strings. To address this problem, we might need to split longer initialization scripts into smaller pieces and compile and link them into the library separately. Experiences with mktclapp suggest that most compilers can handle strings or byte arrays of 64KB and few scripts ever reach that size so perhaps this will not be as big a problem as we suppose.

Surprisingly, adding the initialization scripts to the library will not increase the size of the library by that much. If the initialization scripts are compressed using libz before they are compiled into the Tcl/Tk libraries, the size increase for the library is minimal. For Tcl, the total increase is less than 451KB and for Tk the increase is less than 336KB. Compiling with debugging symbols turned on results in 20 times more bloat.