www.gibmonks.com




  Previous section   Next section

Practical Programming in Tcl & Tk, Third Edition
By Brent B. Welch

Table of Contents
Chapter 44.  C Programming and Tcl


Invoking Scripts from C

The main program is not the only place you can evaluate a Tcl script. You can use the Tcl_Eval procedure essentially at any time to evaluate a Tcl command:

Tcl_Eval(Tcl_Interp *interp, char *command);

The command is evaluated in the current Tcl procedure scope, which may be the global scope. This means that calls like Tcl_GetVar and Tcl_SetVar access variables in the current scope. If for some reason you want a new procedure scope, the easiest thing to do is to call your C code from a Tcl procedure used for this purpose. It is not easy to create a new procedure scope with the exported C API.

graphics/tip_icon.gif

Tcl_Eval modifies its argument.


You should be aware that Tcl_Eval may modify the string that is passed into it as a side effect of the way substitutions are performed. If you pass a constant string to Tcl_Eval, make sure your compiler has not put the string constant into read-only memory. If you use the gcc compiler, you may need to use the -fwritable-strings option. Chapter 45 shows how to get the right compilation settings for your system.

Variations on Tcl_Eval

There are several variations on Tcl_Eval. The possibilities include strings or Tcl_Obj values, evaluation at the current or global scope, a single string (or Tcl_Obj value) or a variable number of arguments, and optional byte-code compilation. The most general string-based eval is Tcl_EvalEx, which takes a counted string and some flags:

Tcl_EvalEx(interp, string, count, flags);

The flags are TCL_GLOBAL_EVAL and TCL_EVAL_DIRECT, which bypasses the byte-code compiler. For code that is executed only one time, TCL_EVAL_DIRECT may be more efficient. Tcl_GlobalEval is equivalent to passing in the TCL_GLOBAL_EVAL flag. The Tcl_VarEval procedure takes a variable number of strings arguments and concatenates them before evaluation:

Tcl_VarEval(Tcl_Interp *interp, char *str, ..., NULL);

Tcl_EvalObj takes an object as an argument instead of a simple string. The string is compiled into byte codes the first time it is used. If you are going to execute the script many times, then the Tcl_Obj value caches the byte codes for you. The general Tcl_Obj value interface to Tcl_Eval is Tcl_EvalObjEx, which takes the same flags as Tcl_EvalEx:

Tcl_EvalObjEx(interp, objPtr, flags);

For variable numbers of arguments, use Tcl_EvalObjv, which takes an array of Tcl_Obj pointers. This routine concatenates the string values of the various Tcl_Obj values before parsing the resulting Tcl command:

Tcl_EvalObjv(interp, objc, objv);

Bypassing Tcl_Eval

In a performance-critical situation, you may want to avoid some of the overhead associated with Tcl_Eval. David Nichols showed me how to call the implementation of a C command procedure directly. The trick is facilitated by the Tcl_GetCommandInfo procedure that returns the address of the C command procedure for a Tcl command, plus its client data pointer. The Tcl_Invoke procedure is shown in Example 44-15. It is used much like Tcl_VarEval, except that each of its arguments becomes an argument to the Tcl command without any substitutions being performed.

For example, you might want to insert a large chunk of text into a text widget without worrying about the parsing done by Tcl_Eval. You could use Tcl_Invoke like this:

Tcl_Invoke(interp, ".t", "insert", "insert", buf, NULL);

Or:

Tcl_Invoke(interp, "set", "foo", "$xyz [blah] {", NULL);

No substitutions are performed on any of the arguments because Tcl_Eval is out of the picture. The variable foo gets the following literal value:

$xyz [blah] {

Example 44-15 shows Tcl_Invoke. The procedure is complicated for two reasons. First, it must handle a Tcl command that has either the object interface or the old string interface. Second, it has to build up an argument vector and may need to grow its storage in the middle of building it. It is a bit messy to deal with both at the same time, but it lets us compare the object and string interfaces. The string interfaces are simpler, but the object interfaces run more efficiently because they reduce copying and type conversions.

Example 44-15 Calling C command procedure directly with Tcl_Invoke.
#include <tcl.h>

#if defined(__STDC__) || defined(HAS_STDARG)
#   include <stdarg.h>
#else
#   include <varargs.h>
#endif

/*
 * Tcl_Invoke --
 *    Directly invoke a Tcl command or procedure
 *
 *    Call Tcl_Invoke somewhat like Tcl_VarEval
 *    Each arg becomes one argument to the command,
 *    with no further substitutions or parsing.
 */
    /* VARARGS2 */ /* ARGSUSED */

int
Tcl_Invoke TCL_VARARGS_DEF(Tcl_Interp *, arg1)
{
   va_list argList;
   Tcl_Interp *interp;
   char *cmd;          /* Command name */
   char *arg;          /* Command argument */
   char **argv;        /* String vector for arguments */
   int argc, i, max;   /* Number of arguments */
   Tcl_CmdInfo info;   /* Info about command procedures */
   int result;         /* TCL_OK or TCL_ERROR */

   interp = TCL_VARARGS_START(Tcl_Interp *, arg1, argList);
   Tcl_ResetResult(interp);

   /*
    * Map from the command name to a C procedure
    */
   cmd = va_arg(argList, char *);
   if (! Tcl_GetCommandInfo(interp, cmd, &info)) {
      Tcl_AppendResult(interp, "unknown command \"",
          cmd, "\"", NULL);
      va_end(argList);
      return TCL_ERROR;
   }

   max = 20;           /* Initial size of argument vector */

#if TCL_MAJOR_VERSION > 7
   /*
    * Check whether the object interface is preferred for
    * this command
    */

   if (info.isNativeObjectProc) {
      Tcl_Obj **objv;     /* Object vector for arguments */
      Tcl_Obj *resultPtr; /* The result object */
      int objc;

      objv = (Tcl_Obj **) Tcl_Alloc(max * sizeof(Tcl_Obj *));
      objv[0] = Tcl_NewStringObj(cmd, strlen(cmd));
      Tcl_IncrRefCount(objv[0]); /* ref count == 1*/
      objc = 1;

      /*
       * Build a vector out of the rest of the arguments
       */

      while (1) {
         arg = va_arg(argList, char *);
         if (arg == (char *)NULL) {
            objv[objc] = (Tcl_Obj *)NULL;
            break;
         }
         objv[objc] = Tcl_NewStringObj(arg, strlen(arg));
         Tcl_IncrRefCount(objv[objc]); /* ref count == 1*/
         objc++;
         if (objc >= max) {
            /* allocate a bigger vector and copy old one */
            Tcl_Obj **oldv = objv;
            max *= 2;
            objv = (Tcl_Obj **) Tcl_Alloc(max *
                   sizeof(Tcl_Obj *));
            for (i = 0 ; i < objc ; i++) {
               objv[i] = oldv[i];
            }
            Tcl_Free((char *)oldv);
         }
      }
      va_end(argList);

      /*
       * Invoke the C procedure
       */
      result = (*info.objProc)(info.objClientData, interp,
             objc, objv);

      /*
       * Make sure the string value of the result is valid
       * and release our references to the arguments
       */
      (void) Tcl_GetStringResult(interp);
      for (i = 0 ; i < objc ; i++) {
         Tcl_DecrRefCount(objv[i]);
      }
      Tcl_Free((char *)objv);

      return result;
   }
#endif
    argv = (char **) Tcl_Alloc(max * sizeof(char *));
    argv[0] = cmd;
    argc = 1;

       /*
        * Build a vector out of the rest of the arguments
        */
       while (1) {
          arg = va_arg(argList, char *);
          argv[argc] = arg;
          if (arg == (char *)NULL) {
             break;
          }
          argc++;
          if (argc >= max) {
             /* allocate a bigger vector and copy old one */
             char **oldv = argv;
             max *= 2;
             argv = (char **) Tcl_Alloc(max * sizeof(char *));
             for (i = 0 ; i < argc ; i++) {
                argv[i] = oldv[i];
             }
             Tcl_Free((char *) oldv);
          }
       }
       va_end(argList);

       /*
        * Invoke the C procedure
        */
        result = (*info.proc)(info.clientData, interp, argc, argv);

       /*
        * Release the arguments
        */
       Tcl_Free((char *) argv);
       return result;

}

This version of Tcl_Invoke was contributed by Jean Brouwers. He uses TCL_VARARGS_DEF and TCL_VARARGS_START macros to define procedures that take a variable number of arguments. These standard Tcl macros hide the differences in the way you do this on different operating systems and different compilers. It turns out that there are numerous minor differences between compilers that can cause portability problems in a variety of situations. Happily, there is a nice scheme used to discover these differences and write code in a portable way. This is the topic of the next chapter.


      Previous section   Next section
    Top