Executable Modules

Executable Modules provide additional functionality to a Tcl interpreter by means of a command that, when called, communicates with an external program and then returns a result.

Executable Modules

With executable modules, the Tcl C API is not used. The external program component of an executable module can be implemented in any language. Sometimes preexisting programs can be put to use in an executable model. All that is required is a procedure that manages the communication with that program. An executable module typically communicates via Tcl command channel created via open. To communicate binary data, use chan configure $comechan -translation binary. Alternatively, shared memory could be used.

Pro:

  1. Any language may be used for an executable module.
  2. Debugging may be easier, because programs have an extra memory boundary.
  3. Testing may be easier, because the program can be tested alone (interactively in many cases).

Con:

  1. Performance may be an issue if lots of I/O is required.
  2. Shared state can be a problem. A possible solution is shared memory.
  3. Complex/structured data can be a problem. Tcl handles serializing very well, but other languages are often weak in this area.
/*
 * By George Peter Staplin
 * This is version 2 of a simple executable-module written in C.
 * This is simply a demonstration.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
 
#define SEPFMT " \t\n"
 
void get_input (char *buf, size_t s) {
  if (NULL == fgets (buf, s, stdin)) {
   if (feof (stdin)) {
    exit (EXIT_SUCCESS);
   } 
   exit (EXIT_FAILURE);
  }
}

int expect_int (void) {
  char *t = strtok (NULL, SEPFMT);
  char *end;
  long l;
 
  if (NULL == t) {
   fprintf (stderr, "premature end of token stream.\n");
   exit (EXIT_FAILURE);
  }
 
  l = strtol (t, &end, 10);
 
  if ((LONG_MIN == l && ERANGE == errno)
    || (end < (t + (strlen (t) - 1)))) {
   fprintf (stderr, "invalid integer '%s'\n", t);
   exit (EXIT_FAILURE);
  }
 
  return (int) l;
}

int main (int argc, char *argv[]) {
  char buf[1024];
  char *tok;
 
 again:
  get_input (buf, sizeof (buf));
 
  tok = strtok (buf, SEPFMT);
 
  if (NULL == tok || 1 != strlen (tok)) {
   fprintf (stderr, "invalid operator: '%s'\n", tok);
   exit (EXIT_FAILURE);
  }
 
  switch (tok[0]) {
   case '+':
    printf ("%d\n", expect_int () + expect_int ());
   break;
  
   case '-':
   {
    int a, b;
    a = expect_int ();
    b = expect_int ();
    printf ("%d\n", a - b);
   }
   break;
 
   default:
    fprintf (stderr, "unknown operator: '%c'\n", tok[0]);
    exit (EXIT_FAILURE);
  }
  goto again;
 
  return EXIT_FAILURE;
}

To use the code above as a module we will use a two-way pipe. Tcl uses the | character in open to create channel between to the program. For two-way communication, use the w+ flag. gets is used to retrieve the result. read could also be used, but it would require a fixed-length format, non-blocking I/O, or a read of a single character until \n is reached.

#! /bin/env tclsh

set ::mathModule [open {|./modmath} w+]

proc modMath {args} {
    puts -nonewline "$args is "
    puts $::mathModule $args
    flush $::mathModule
    return [gets $::mathModule]
}

proc main {} {
    puts [modMath + 200 300]
    puts [modMath - 5600 1243]
    puts Done
}
main