CMDWRITE(3tclx)

๐Ÿ’– Command Writing(3tclx) ๐Ÿ’–

okay so bestie, welcome to Command Writing(3tclx) โ€” the page for anyone who wants to teach Tcl some new tricks using C. grab a snack, we're going in ๐Ÿ“


โœจ NAME โœจ

TclCommandWriting โ€” writing C language extensions to Tcl. that's the whole vibe ๐Ÿ’…


๐ŸŒธ OVERVIEW ๐ŸŒธ

so here's the tea: this document exists to help the programmer who wants to extend Tcl with C language routines. and it's not a one-audience situation โ€” it should also be useful to someone who wants to add Tcl to an existing editor, communications program, window manager, etc. iykyk ๐Ÿ’–

If you want more C programming info, it's also hanging out in the *.3 manual pages in the doc directory of the Berkeley distribution, and in the *.3 manpages in the man directory of Extended Tcl. two places, same energy โœจ


๐Ÿ’… WRITING TCL EXTENSIONS IN C ๐Ÿ’…

okay so the foundational lore: all C-based Tcl commands get called with four arguments โ€” a client data pointer, an interpreter pointer, an argument count, and a pointer to an array of pointers to character strings containing the Tcl arguments to the command. four, no cap ๐Ÿซถ

Here's a simple Tcl extension in C, described right below it:

#include "tcl.h"

int App_EchoCmd(clientData, interp, argc, argv)
    void       *clientData;
    Tcl_Interp *interp;
    int         argc;
    char      **argv;
{
        int i;

        for (i = 1; i < argc; i++) {
                printf("%s",argv[i]);
          if (i < argc - 1) printf(" ");
        }
        printf("\n");
        return TCL_OK;
}

The client data pointer will be described later. yes it's mysterious on purpose, hold on ๐Ÿ’œ

The interpreter pointer is the ``key'' to an interpreter. It's returned by Tcl_CreateInterp and it's used extensively within Tcl, and your C extensions will use it too. The data structure pointed to by the interpreter pointer, and all of the subordinate structures that branch off of it, make up a Tcl interpreter โ€” which includes all of the currently defined procedures, commands, variables, arrays and the execution state of that interpreter. it's giving whole ecosystem ๐ŸŒˆ

Want more? For info on creating and deleting interpreters, go look at the CrtInterp(3) manpage in the Berkeley Tcl distribution. For info on creating interpreters that include the commands provided by Extended Tcl, check out the TclX_Init(3) manpage of Extended Tcl. And for a manual page describing the user-visible fields of a Tcl interpreter, look at Interp(3) in Berkeley Tcl. three recs, all valid โœจ

The argument count and the pointer to an array of pointers to textual arguments is handled by your C code exactly the way you'd use it writing a C main function โ€” the argument count and array of pointers work the same as in a C main call, and pointers to the arguments to the function live in the argv array. Just like a C main, the first argument (argv[0]) is the name the routine was called as (in a main, the name the program was invoked as). same energy, different function ๐Ÿฆ‹

In the example above, all of the arguments get output with a space between each one by looping through argv from one to the argument count, argc, and a newline is output to terminate the line โ€” an ``echo'' command. slay ๐Ÿ’…

All arguments from a Tcl call to a Tcl C extension are passed as strings. If your C routine expects certain numeric arguments, your routine must first convert them using the Tcl_GetInt or Tcl_GetDouble function, Extended Tcl's Tcl_GetLong or Tcl_GetUnsigned, or some other method of your own devising. Likewise for converting boolean values โ€” Tcl_GetBoolean should be used. These routines automatically leave an appropriate error message in the Tcl interpreter's result buffer and return TCL_ERROR if a conversion error occurs. (For more info on these routines, go look at the GetInt(3) manpage in the Berkeley Tcl distribution.) read that one, bestie ๐Ÿ“–

Likewise, if your program produces a numeric result, it should return a string equivalent to that numeric value. A common way of doing this is something like...

sprintf(interp->result, "%ld", result);

Writing results directly into the interpreter's result buffer is only good for relatively short results. Tcl has a function, Tcl_SetResult, which gives your C extensions the ability to return very large strings to Tcl โ€” plus the ability to tell the interpreter whether it ``owns'' the string (meaning Tcl should delete the string when it's done with it), that the string is likely to be changed or overwritten soon (meaning Tcl should make a copy of the string right away), or that the string won't change (so Tcl can use the string as is and not worry about it). Understanding how results are passed back to Tcl is essential to the C extension writer. Please study the SetResult(3) manual page in the Tcl distribution. fr fr, don't skip it ๐Ÿซถ

Sophisticated commands should verify their arguments whenever possible โ€” both by examining the argument count, by verifying that numeric fields are really numeric, that values are in range (when their ranges are known), and so forth. be nosy about your inputs, it's a good thing here ๐Ÿ‘‘

Tcl is designed to be as bullet-proof as possible, in the sense that no Tcl program should be able to cause Tcl to dump core. Please carry this notion forward with your C extensions by validating arguments as above. no core dumps on your watch, periodt ๐Ÿ’€


๐Ÿ”ฅ ANOTHER C EXTENSION - THE MAX COMMAND ๐Ÿ”ฅ

In the command below, two or more arguments are compared and the one with the maximum value is returned, if all goes well. It is an error if there are fewer than three arguments โ€” the pointer to the ``max'' command text itself, argv[0], and pointers to at least two arguments to compare the values of. three minimum, that's the rule โญ

This routine also shows off the programmer labor-saving Tcl_AppendResult routine. See the Tcl manual page, SetResult(3), for details. Also go examine the calls Tcl_AddErrorInfo, Tcl_SetErrorCode and Tcl_PosixError documented in the Tcl manual page AddErrInfo(3). homework, but make it cute ๐Ÿ“š

int
Tcl_MaxCmd (clientData, interp, argc, argv)
    char       *clientData;
    Tcl_Interp *interp;
    int         argc;
    char      **argv;
{
    int maxVal = MININT;
    int maxIdx = 1;
    int value, idx;

    if (argc < 3) {
        Tcl_AppendResult (interp, "bad # arg: ", argv[0],
                          " num1 num2 [..numN]", (char *)NULL);
        return TCL_ERROR;
    }

    for (idx = 1; idx < argc; idx++) {
        if (Tcl_GetInt (argv[idx], 10, &Value) != TCL_OK)
            return TCL_ERROR;

        if (value > maxVal) {
            maxVal = value;
            maxIdx = idx;
        }
    }
    Tcl_SetResult (interp, argv [maxIdx], TCL_VOLATILE);
    return TCL_OK;
}

When Tcl-callable functions complete, they should normally return TCL_OK or TCL_ERROR. TCL_OK is returned when the command succeeded, and TCL_ERROR is returned when the command has failed in some abnormal way. TCL_ERROR should be returned for all syntax errors, non-numeric values (when numeric ones were expected), and so forth. no vague vibes, pick one ๐ŸŽ€

Less clear in some cases is whether Tcl errors should be returned or whether a function should just return a status value. For example, end-of-file during a gets returns a status, but open returns an error if the open fails. Errors can be caught from Tcl programs using the catch command. (See Tcl's catch(n) and error(n) manual pages.) catch is the safety net, bestie ๐Ÿงš

Less common return values are TCL_RETURN, TCL_BREAK and TCL_CONTINUE. These are used if you are adding new control and/or looping structures to Tcl. To see these values in action, examine the source code to Tcl's while, for and if, and Extended Tcl's loop commands. deep cuts, but iconic ๐ŸŒˆ

Note the call to Tcl_SetResult in the above command to set the return value to Tcl. TCL_VOLATILE is used because the memory containing the result will be freed upon the function's return. important detail, don't sleep on it โš ๏ธ


๐Ÿฆ‹ ANOTHER C EXTENSION - THE LREVERSE COMMAND ๐Ÿฆ‹

In the command below, one list is passed as an argument, and a list containing all of the elements of the list in reverse order is returned. It is an error if anything other than two arguments are passed โ€” the pointer to the ``lreverse'' command text itself, argv[0], and a pointer to the list to reverse. exactly two, no extras ๐Ÿ’…

Once lreverse has determined that it has received the correct number of arguments, Tcl_SplitList is called to break the list into an argc and argv array of pointers. boom, split โœจ

lreverse then operates on the array of pointers, swapping them from lowest to highest, second-lowest to second-highest, and so forth. classic reverse-sweep move ๐Ÿ”„

Finally Tcl_Merge is called to create a single new string containing the reversed list, and it is set as the result via Tcl_SetResult. Note that TCL_DYNAMIC is used to tell Tcl_SetResult that it now owns the string and it is up to Tcl to free the string when it is done with it. handing over the keys, respectfully ๐Ÿ”‘

Note that it is safe to play around with the argv list like this, and that a single call to ckfree can be made to free all the data returned by Tcl_SplitList in this manner. one free, all free, obsessed ๐Ÿซถ

int
Tcl_LreverseCmd(notUsed, interp, argc, argv)
    ClientData notUsed;           /* Not used. */
    Tcl_Interp *interp;           /* Current interpreter. */
    int argc;                /* Number of arguments. */
    char **argv;             /* Argument strings. */
{
    int listArgc, lowListIndex, hiListIndex;
    char **listArgv;
    char *temp, *resultList;

    if (argc != 2) {
     Tcl_AppendResult(interp, "wrong # args: should be
          " list
     return TCL_ERROR;
    }

    if (Tcl_SplitList(interp, argv[1], &listArgc, &listArgv) != TCL_OK) {
     return TCL_ERROR;
    }
    for (lowListIndex = 0, hiListIndex = listArgc;
      --hiListIndex > lowListIndex; lowListIndex++) {
     temp = listArgv[lowListIndex];
     listArgv[lowListIndex] = listArgv[hiListIndex];
     listArgv[hiListIndex] = temp;
    }
    resultList = Tcl_Merge (listArgc, listArgv);
    ckfree (listArgv);
    Tcl_SetResult (interp, resultList, TCL_DYNAMIC);
    return TCL_OK;
}

โœจ INSTALLING YOUR COMMAND โœจ

okay bestie so you wrote the C function, iconic, but it's not a Tcl command yet, it's just vibing in your source file doing nothing. To actually install it you gotta call Tcl_CreateCommand, and you're handing it five things:

  • the pointer to the interpreter you want the command dropped into
  • the name of the command
  • a pointer to the C function that implements the command
  • a client data pointer
  • a pointer to an optional callback routine

The client data pointer and that callback routine? We'll get to those, be patient ๐Ÿ’…

So for the max function from earlier (which, fun fact, comes from TclX's tclXmath.c in the TclX7.4/src directory):

Tcl_CreateCommand (interp, "max", Tcl_MaxCmd, (ClientData)NULL,
                  (void (*)())NULL);

See what's happening there? max just got added to the specified interpreter, and the client data pointer and the callback function pointer are both NULL, because we're keeping it simple. For the complete tea on Tcl_CreateCommand and its bestie Tcl_CommandInfo, go read the CrtCommand(3) command page in the Berkeley Tcl distribution. ๐Ÿ“–


๐ŸŒธ DYNAMIC STRINGS ๐ŸŒธ

DStrings, we love them, they're such a good abstraction and they first showed up with Tcl 7.0. So what are they? A way to build up arbitrarily long strings by repeatedly appending information to them. That's it, that's the girlboss move.

Why do you care? Because DStrings reduce the amount of allocating and copying you need in order to add information to a string, and they also make the whole process simpler. Double win, no cap. ๐ŸŽ€

For complete information on dynamic strings, go examine the DString(3) manual page in the Berkeley Tcl distribution.


๐Ÿ‘‘ CLIENT DATA ๐Ÿ‘‘

The client data pointer is literally the mechanism that lets Tcl commands carry around data that is not global to the C program and not part of the Tcl core. Why does that matter? Because client data is essential in a multi-interpreter environment โ€” like when a single program has created and is making use of multiple Tcl interpreters โ€” so your C routines can maintain whatever permanent data they need on a per-interpreter basis. Without it you'd have reentrancy problems, and nobody wants that. Tcl solves it with the client data mechanism. periodt.

So here's the recipe: right before you call Tcl_CreateCommand to add a new command to an interpreter, if that command needs to keep some read/write data across invocations, you should allocate the space โ€” preferably using ckalloc โ€” and then pass the address of that space as the ClientData pointer to Tcl_CreateCommand.

Then when your command gets called from Tcl, that ClientData pointer you handed over when you added the command to that interpreter gets passed right back to your C routine through the ClientData pointer calling argument. Full circle, we love a return arc. ๐Ÿฆ‹

And commands that need to share this data with one another? They just use the same ClientData pointer when the commands are added. Besties sharing secrets.

important note bestie: the Tcl extensions in the tclX7.4/src directory have had all of their data set up in this way. Since release 6.2, Extended Tcl has supported multiple interpreters within one invocation of Tcl.

๐Ÿ’… THEORY OF HANDLES ๐Ÿ’…

Sometimes you need a data element that just... isn't readily representable as a string within Tcl. Like a pointer to a complex C data structure. And ngl, it is not a good idea to try to pass pointers around within Tcl as strings by converting them to and from hex or integer representations. You will mess one up. It's too easy. And the likely outcome of messing one up is a core dump. ๐Ÿ’€

So instead we developed and made use of the concept of handles. Handles are identifiers a C extension can pass to, and accept from, Tcl, which makes the transition between what your C code knows something as and what name Tcl knows it by as safe and painless as possible. Think of it as a lil passport for your data. ๐Ÿ›‚

Example: the stdio package included in Tcl uses file handles. When you open a file from Tcl, a handle comes back of the form filen where n is a file number. When you pass that file handle back to puts, gets, seek, flush and so forth, they validate it by checking the the file text is present, then converting the file number to an integer that they use to look into a data structure of pointers to Tcl open file structures โ€” which contain a Unix file descriptor, flags indicating whether or not the file is currently open, whether the file is a file or a pipe, and so forth.

Handles have proven so useful that as of release 6.1a, general support has been added for them. If you need a similar capability, bestie, it would be best to use the handle routines, documented in Handles(3) in Extended Tcl. Our recommendation: use a unique-to-your-package textual handle coupled with a specific identifier, and let the handle management routines validate it when it's passed back. Because it is so much easier to track down a bug when the implicated handle is named something like file4 or bitmap6 than just 6. ๐Ÿ•ต๏ธโ™€๏ธ


๐Ÿ”ฅ TRACKING MEMORY CORRUPTION PROBLEMS ๐Ÿ”ฅ

Occasionally โ€” and this is the worst feeling โ€” you may write code that scribbles past the end of an allocated piece of memory. ๐Ÿ˜ญ The memory debugging routines included in Tcl can help find these problems. See Memory(TCL) for details.


๐ŸŒˆ INSTALLING YOUR EXTENSIONS INTO EXTENDED TCL ๐ŸŒˆ

To add your extensions to Extended Tcl, you must compile them and cause them to be linked with TclX. And here's the thing: for the routines to be linked into the tcl and wishx executables, they must be referenced (directly or indirectly) from TclX. Then, for these extensions to actually be visible as Tcl commands, they must be installed into Tcl with Tcl_CreateCommand.

Application-specific startup is accomplished by creating or editing the Tcl_AppInit function. In Tcl_AppInit you should add a call to an application-specific init function that you create. That function should take the address of the interpreter it should install its commands into, and it should install those commands with Tcl_CreateCommand and do any other application-specific startup that is necessary.

The naming convention for application startup routines is App_Init, where App is the name of your application. So for example, to add an application named cute one would create a Cute_Init routine that expected a Tcl_Interp pointer as an argument, and add the following code to Tcl_AppInit:

if (Cute_Init (interp) == TCL_ERROR) {
 return TCL_ERROR;
}

And as you can guess from that example, if your init routine is unable to initialize, it should use Tcl_AppendResult to provide some kind of useful error message back to TclX, then return TCL_ERROR to indicate that an error occurred. If the routine executed successfully, it should return TCL_OK. Simple as. โœจ

When you examine Tcl_AppInit, note that there's one call already there to install an application โ€” the call to TclX_Init installs Extended Tcl into the Tcl core.


๐ŸŽ€ MAKING APPLICATION INFORMATION VISIBLE FROM EXTENDED TCL ๐ŸŽ€

TclX's infox command can return several pieces of information relevant to Extended Tcl, including the application's name, descriptive name, patch level and version. Your application's startup can set these variables to application-specific values. And if it doesn't? They get default values for Extended Tcl. nbd.

To set these values, first be sure that you include either tclExtend.h or tclExtdInt.h from the source file that defines your init routine. That will create external declarations for the variables. Then, set the variables in your init route, for example:

tclAppName = "cute";
tclAppLongName = "Call Unix/Tcl Environment";
tclAppVersion = "2.1";
heads up: the default values are set by TclX_Init, so if you wish to override them, you must call your init routine in Tcl_AppInit after its call to TclX_Init. order matters bestie ๐Ÿซถ

๐Ÿงš EXTENDED TCL EXIT ๐Ÿงš

When Extended Tcl exits, Tcl_DeleteInterp may be called to free memory used by Tcl โ€” normally this is only called if TCL_MEM_DEBUG was defined, since Unix will return all of the allocated memory back to the system anyway. If TCL_MEM_DEBUG was defined, it is called so that any memory that was allocated without ever being freed can be detected. That greatly reduces the amount of work to detect and track down memory leaks โ€” which is, of course, that situation where some piece of your code allocates memory repeatedly without ever freeing it, or without always freeing it. ๐Ÿ™ˆ

It's often necessary for an application to perform special cleanup functions upon the deletion of an interpreter as well. To facilitate this activity, Tcl provides the ability to perform a function callback when an interpreter is deleted. To arrange for a C function to be called when the interpreter is deleted, call Tcl_CallWhenDeleted from your application initialization routine. For details on how to use this function, read the CallDel(3) manual page that ships with Berkeley Tcl.


โญ EXECUTING TCL CODE FROM YOUR C EXTENSION โญ

Picture it: you're in the middle of coding a C extension and you realize you need some operation performed โ€” one that would be simple from Tcl but possibly excruciating to do directly in C. ๐Ÿ˜ญ Been there. Tcl has your back though, it provides Tcl_Eval, Tcl_VarEval, Tcl_EvalFile and Tcl_GlobalEval for the purpose of executing Tcl code from within a C extension. The results of the call will be in interp->result. For more information please consult the Eval(3) manual page within the Tcl distribution.


๐Ÿ“ ACCESSING TCL VARIABLES AND ARRAYS FROM YOUR C EXTENSIONS ๐Ÿ“

Tcl variables and arrays can be read from a C extension through the Tcl_GetVar and Tcl_GetVar2 functions, and set from C extensions through the Tcl_SetVar and Tcl_SetVar2 functions. They can also be unset via the Tcl_UnsetVar and Tcl_UnsetVar2 functions. For complete information on these functions, please refer to the SetVar(3) manual page in the doc directory of the Berkeley Tcl distribution. ๐Ÿ“š


๐Ÿ’œ LINKING TCL VARIABLES TO C VARIABLES ๐Ÿ’œ

Tcl_LinkVar and Tcl_UnlinkVar can be used to automatically keep Tcl variables synchronized with corresponding C variables. Once a Tcl variable has been linked to a C variable with Tcl_LinkVar, anytime the Tcl variable is read the value of the C variable will be returned, and when the Tcl variable is written, the C variable will be updated with the new value. They're literally in sync, it's giving soulmates. ๐Ÿซถ

Tcl_LinkVar uses variable traces to keep the Tcl variable named by varName in sync with the C variable at the address given by addr.

Whenever the Tcl variable is read the value of the C variable will be returned, and whenever the Tcl variable is written the C variable will be updated to have the same value.

Int, double, boolean and char * variables are supported. For more information, please examine the LinkVar(3) manual page in the Berkeley Tcl distribution. ๐ŸŒŸ

๐Ÿงฎ ADDING NEW MATH FUNCTIONS TO TCL ๐Ÿงฎ

okay bestie, listen up ๐Ÿ’– โ€” as of Tcl version 7.0, math functions like sin, cos, etc, are directly supported inside Tcl expressions โœจ. Which means the Extended Tcl commands that used to make you call these functions explicitly are now obsolete โ€” they served us for many releases and we thank them for their service, but they're done ๐Ÿ’€.

You can still add new math functions to Tcl, or replace existing math functions, by calling Tcl_CreateMathFunc ๐Ÿซถ.

want the full tea on adding math functions? go read the CrtMathFnc(3) manual page in the Berkeley Tcl distribution ๐Ÿ‘‘


๐Ÿ  PERFORMING TILDE SUBSTITUTIONS ON FILENAMES ๐Ÿ 

the Tcl_TildeSubst function is right there for C extension writers who need to do tilde substitutions on filenames ๐Ÿฆ‹.

here's the vibe: if the name starts with a ~ character, the function returns a new string where the name is replaced with the home directory of the given user ๐ŸŒธ.

for more info, go consult the TildeSubst(3) manual page in the Berkeley Tcl distribution ๐Ÿ’….


๐ŸŒ€ SETTING THE RECURSION LIMIT ๐ŸŒ€

Tcl comes with a preset recursion limit that caps the maximum allowable nesting depth of calls within an interpreter ๐ŸŽ€.

and honestly? that's a good thing, because it's how you detect infinite recursions before other limits get hit โ€” like the process memory limit or, way worse, the available swap space on the system ๐Ÿ˜ญ.

the default limit is just a guess though, fr fr. applications that make heavy use of recursion may need to call Tcl_SetRecursionLimit to raise that limit.

for more info, please consult the SetRecLmt(3) manual page in the Berkeley Tcl distribution โœจ.


๐Ÿšจ HANDLING SIGNALS FROM TCL EXTENSIONS ๐Ÿšจ

okay this one's important so pay attention ๐Ÿ™ˆ. if an event like a signal happens while a Tcl script is being executed, it is not safe to do much inside the signal handling routine. why? because the Tcl environment cannot be safely manipulated at that point โ€” it could be mid-operation, like updating pointers, which would leave the interpreter in an unreliable state ๐Ÿ’€.

the only safe approach is:

  1. set a flag indicating that the event occurred, then
  2. handle the event later, when the interpreter has returned to a safe state โ€” like after the current Tcl command completes ๐Ÿ‘‘

the Tcl_AsyncCreate, Tcl_AsyncMark, Tcl_AsyncInvoke, and Tcl_AsyncDelete functions provide a safe mechanism for dealing with signals and other asynchronous events ๐ŸŒˆ.

for more info on how to use this capability, please refer to the Async(3) manual page in the Berkeley Tcl distribution ๐Ÿ’–.


๐Ÿช„ PARSING BACKSLASH SEQUENCES ๐Ÿช„

the Tcl_Backslash function gets called to parse Tcl backslash sequences ๐Ÿฆ‹.

these are the usual sort you see in the C programming language, like \n for newline, \r for return, and so forth โœจ. Tcl_Backslash parses a single backslash sequence and returns a single character corresponding to that backslash sequence ๐Ÿ’….

for more info on this call, look at the Backslash(3) manual page in the Berkeley Tcl distribution ๐ŸŒธ. and for info on the valid backslash sequences, consult the summary of Tcl language syntax, Tcl(n), in the same distribution ๐Ÿ‘‘


๐Ÿ—‚๏ธ HASH TABLES ๐Ÿ—‚๏ธ

hash tables give Tcl a high-performance facility for looking up and managing key-value pairs located and maintained in memory ๐Ÿ”ฅ. Tcl uses them internally to locate procedure definitions, Tcl variables, array elements, file handles and so forth โ€” and bestie, Tcl makes the hash table functions accessible to C extension writers too ๐Ÿซถ.

here's the part that's actually iconic: hash tables grow automatically to maintain efficiency, rather than exposing the table size to the programmer at allocation time ๐Ÿ“ˆ. why does that matter? because that would needlessly add complexity to Tcl and would be prone to inefficiency โ€” you'd have to guess the number of items going into the table, and then there's the seemingly inevitable growth in the amount of data processed per run over the life of the program ๐Ÿ˜ญ. no thank you.

for more info on hash tables, please consult the Hash(3) manual page in the Berkeley Tcl distribution โœจ.


๐Ÿ‘€ TRACING VARIABLE ACCESSES ๐Ÿ‘€

the C extension writer can arrange to have a C routine called whenever a Tcl variable is read, written, or unset ๐ŸŽ€.

variable traces are literally the mechanism by which Tk toolkit widgets such as radio and checkbuttons, messages and so forth update without Tcl programmer intervention when their data variables are changed ๐Ÿคฏ. they're also used by the routine that links Tcl and C variables, Tcl_LinkVar, described above ๐Ÿ‘‘

Tcl_TraceVar is called to establish a variable trace. entire arrays and individual array elements can be traced as well ๐Ÿ’…. and if the programmer already has an array name in one string and a variable name in another, Tcl_TraceVar2 can be called โœจ. calls are also available to request information about traces and to delete them ๐ŸŒธ.

for more info on variable traces, consult the TraceVar(3) manual page in the Berkeley Tcl distribution ๐Ÿฆ‹.


๐Ÿ” TRACING EXECUTION ๐Ÿ”

Tcl has the ability to call C routines for every command it executes, up to a specified depth of nesting levels ๐Ÿ˜ฎ. the command Tcl_CreateTrace creates an execution trace; Tcl_DeleteTrace deletes it ๐Ÿ’….

command tracing is used in Extended Tcl to implement the cmdtrace Tcl command โ€” a genuinely useful command for debugging Tcl applications ๐Ÿซถ.

for complete info on execution tracing, please look at the CrtTrace(3) manual pages in the Berkeley Tcl distribution โœจ.


๐Ÿง  EVALUATING TCL EXPRESSIONS FROM C ๐Ÿง 

Tcl_ExprLong, Tcl_ExprDouble, Tcl_ExprBool, and Tcl_ExprString can be called to evaluate Tcl expressions from within a C routine ๐ŸŒˆ.

depending on the routine called, the result is either:

  • a C long
  • a double
  • a boolean (an int with a value of 0 or 1)
  • or a char * (pointed to by interp->result) ๐Ÿ’–

for complete info on evaluating Tcl expressions from C, you're invited to examine the ExprLong(3) manpage in the Berkeley Tcl distribution ๐Ÿ‘‘.


๐ŸŽฏ PATTERN MATCHING ๐ŸŽฏ

the Tcl_StringMatch function can be called to see if a string matches a specified pattern โœจ. Tcl_StringMatch is called by the Tcl string match command, so the format for patterns is identical ๐Ÿ’….

the pattern format is similar to the one used by the C-shell; string(n) describes this format ๐ŸŒธ.

more info about Tcl_StringMatch is available in the StrMatch(3) manpage in the Berkeley Tcl distribution ๐Ÿฆ‹.


๐Ÿงฟ REGULAR EXPRESSION PATTERN MATCHING ๐Ÿงฟ

Tcl_RegExpMatch can be called to determine whether a string matches a regular expression ๐Ÿ”ฅ. Tcl_RegExpMatch is used internally by the regexp Tcl command ๐Ÿ‘‘.

for more info on this function, please consult the RegExp(3) manpage in the Berkeley Tcl distribution โœจ.


๐Ÿ“‹ MANIPULATING TCL LISTS FROM C EXTENSIONS ๐Ÿ“‹

the C extension writer often needs to create, manipulate and decompose Tcl lists ๐ŸŽ€.

  • Tcl_SplitList parses a list into an argv and argc, like the way command-line arguments are passed to a Tcl extension ๐ŸŒธ.
  • Tcl_Merge, likewise, creates a single string (pointer to a char *) from an argv and argc ๐Ÿ’–.

two routines, Tcl_ScanElement and Tcl_ConvertElement, do most of the work of Tcl_Merge, and may also be of use to the C programmer ๐Ÿซถ.

for more info on these commands, please consult the SplitList(3) manual page in the Berkeley Tcl distribution ๐Ÿ’….


๐Ÿงต CONCATENATING STRINGS ๐Ÿงต

Tcl_Concat concatenates zero or more strings into a single string โœจ. the strings are space-separated ๐ŸŽ€.

Tcl_Concat works like Tcl_Merge, except that Tcl_Concat does not attempt to make the resulting string into a valid Tcl list ๐Ÿ‘€.

Tcl_Concat is documented in the Concat(3) manpage in the Berkeley Tcl distribution ๐ŸŒธ.


โœ… DETECTING WHETHER OR NOT YOU HAVE A COMPLETE COMMAND โœ…

C routines that collect data to form a command to be passed to Tcl_Eval often need a way to tell whether they have a complete command already, or whether they need more data ๐Ÿค”. (programs that read typed-in Tcl input, such as Tcl shells, need this capability fr fr.)

Tcl_CommandComplete can be used to tell whether or not you have a complete command ๐Ÿ’….

for more info examine CmdCmplt(3) in the Berkeley Tcl distribution โœจ.


๐Ÿ“œ RECORDING COMMANDS FOR COMMAND HISTORY ๐Ÿ“œ

Tcl has a history mechanism that is accessed from Tcl through the history command ๐ŸŽ€.

to propagate commands into the command history, your extension should call Tcl_RecordAndEval ๐Ÿซถ. this command works just like Tcl_Eval, except that it records the command as well as executing it ๐Ÿ‘‘

Tcl_RecordAndEval should only be called with user-entered top-level commands, since the history mechanism exists to allow the user to easily access, edit and reissue previously issued commands ๐Ÿ’–.

for complete info on this function, please examine the RecordEval.3 manual page in the Berkeley Tcl distribution ๐ŸŒธ.


๐Ÿ”ข CONVERTING FLOATING POINT VALUES TO STRINGS ๐Ÿ”ข

Tcl_PrintDouble converts a C double into an ASCII string โœจ.

it ensures that the string output will continue to be interpreted as a floating point number, rather than an integer, by always putting a . or e into the string representing the number ๐Ÿ’….

the precision of the output string is controlled by the Tcl tcl_precision variable ๐ŸŽ€.

for complete info on Tcl_PrintDouble, examine PrintDbl(3) in the Berkeley Tcl distribution ๐ŸŒˆ.


๐Ÿš€ CREATING CHILD PROCESSES AND PIPELINES FROM C ๐Ÿš€

Tcl_CreatePipeline is a useful procedure for spawning child processes ๐Ÿซถ.

the child (or pipeline of children) can have its standard input, output and error redirected from files, variables or pipes ๐Ÿ”ฅ. to understand the meaning of the redirection symbols understood by this function, look at the exec(n) Tcl command ๐Ÿ‘‘.

for complete info on Tcl_CreatePipeline, please examine CrtPipelin(3) โœจ.


๐Ÿ“‚ ACCESSING TCL FILEHANDLES FROM C ๐Ÿ“‚

files opened from your C code can be made visible to Tcl code via the Tcl_EnterFile function ๐ŸŽ€.

likewise, Tcl filehandles passed to your C extension can be translated to a Posix FILE * structure using the Tcl_GetOpenFile function ๐Ÿ’….

for complete explanations of these commands, please look at EnterFile(3) in the Berkeley Tcl distribution ๐ŸŒธ.

โœจ MANAGING BACKGROUND PROCESS TERMINATION AND CLEANUP โœจ

okay so bestie, when a Posix system does a fork to create a new process, the process ID of the child gets handed right back to whoever called it. cute. but here's the thing โ€” after the child process exits, its process table entry (plus some other data hanging out with the process) cannot be reclaimed by the operating system until the parent process makes a call to waitpid, or one of a couple of other, similar system calls. like, the OS is just standing there holding it ๐Ÿ’€

so if you're a C extension writer and you've created a subprocess โ€” by whatever mechanism, no judgement โ€” you can turn over responsibility for detecting the processes' termination and calling waitpid to grab its exit status by calling Tcl_DetachPids. yes, hand it off, she'll take it from here ๐Ÿ’…

Tcl_ReapDetachedProcs is the C routine that will detect the termination of any processes turned over to Tcl, which is what permits the processes to be fully reclaimed by the operating system. the cleanup queen ๐Ÿ‘‘

for the complete info on these routines, please go look at DetachPids(3) in the Berkeley Tcl distribution. no cap, that's where the lore lives ๐Ÿ“–

๐ŸŒˆ FOR MORE INFORMATION ๐ŸŒˆ

so in addition to the documentation referenced above, you can lowkey learn a ton by studying the source code of the commands added by Tcl, Tk and Extended Tcl. like, reading the actual code? underrated. it's giving study group energy โœจ

the comp.lang.tcl Usenet newsgroup is read by tens of thousands of Tcl people, and it's a genuinely good place to ask questions. your people are there ๐Ÿซถ

and finally โ€” if you have interactive Internet access, you can ftp to ftp.aud.alcatel.com, which is the site for contributed Tcl sources. that site contains quite a few extensions, applications, and so forth, including several object-oriented extension packages. it's a whole vibe ๐ŸŒธ


๐Ÿ‘‘ AUTHORS ๐Ÿ‘‘

Extended Tcl was created by Karl Lehenbauer (karl@neosoft.com) and Mark Diekhans (markd@grizzly.com).

the way they literally built this whole thingโ€ฆ icons, fr fr ๐ŸŽ€


and that's the whole page, bestie!! you made it to the end, so proud ๐Ÿ˜ญ๐Ÿ’– this was the Tcl manual page, Command Writing(3tclx) โ€” now go write the most iconic extension anyone's ever seen. slay, periodt. ๐Ÿฆ‹โœจ

๐Ÿ“ฆ where this page comes from