first previous next last contents

Returning a Result

To return a result to Tcl the interp->result variable needs to be set. This can be done in a variety of ways including setting the result manually or using a function such as Tcl_SetResult, Tcl_AppendResult, Tcl_ResetResult or Tcl_DStringResult.

However the choice of which to use is not as obvious as may first appear. A cautionary tale will illustrate some of the easy pitfalls. The following points are not made sufficiently clear in John Ousterhouts Tcl and Tk book. Additionally the problems are real and have been observed in the development of Gap4.

Consider the case where we have many commands registered with the interpreter. One such example could be:

int example(ClientData clientData, Tcl_Interp *interp, int argc, char **argv)
{
    /* ... */

    sprintf(interp->result, "%d", some_c_func());
    return TCL_OK;
}

Now deep within some_c_func we have a Tcl_Eval call which happens to end with something like the following:

proc some_tcl_func {} {
    # ...

    set fred jim
}

Due to the call of Tcl_Eval in some_c_func the interp->result is now set to the last returned result, which is from the set command. In the above example interp->result points to 'jim'. The sprintf command in the example function will overwrite this string and hence change the value of the fred Tcl variable. This causes confusion and in some cases may also cause memory corruption where data is incorrectly freed.

The moral of this tale is to be extremely wary. As there is no knowledge of what some_c_func does (and remember it may get updated later) we seem to trapped. One possible solution is to rewrite the example function as follows.

int example(ClientData clientData, Tcl_Interp *interp, int argc, char **argv)
{
    int ret;
    /* ... */

    ret = some_c_func();
    Tcl_ResetResult(interp);
    sprintf(interp->result, "%d", ret);
    return TCL_OK;
}

This leads to another pitfall. If we have 'sprintf(interp->result, "%d", some_c_func(interp));' and some_c_func calls (possibly indirectly) the Tcl_ResetResult function then we'll be modifying the interp->result address. This leads to undefined execution of code. (Is sprintf passed the original or final interp->result pointer?)

Therefore I'm inclined to think that we should never use Tcl_ResetResult except immediately before a modification of interp->result in a separate C statement. My personal recommendation is to never write directly to interp->result. Additionally never reset interp->result to a new string unless interp->freeProc is also updated correctly. In preference, use Tcl_SetResult.

The Tcl_SetResult function should always work fine, however it does not take printf style arguments. We have implemented a vTcl_SetResult which takes an interp argument and the standard printf format and additional arguments. For instance we would rewrite the example function as the following

int example(ClientData clientData, Tcl_Interp *interp, int argc, char **argv)
{
    int ret;
    /* ... */

    vTcl_SetResult(interp, "%d", some_c_func());
    return TCL_OK;
}

As a final note on vTcl_SetResult; the current implementation only allows strings up to 8192 bytes. This should be easy to remedy if it causes problems for other developers.


first previous next last contents
This page is maintained by staden-package. Last generated on 1 March 2001.
URL: http://www.mrc-lmb.cam.ac.uk/pubseq/manual/scripting_204.html