Tcl_CreateObjCommand

TR - This C function is used to create a new command usable from the Tcl script level. The definition is

Tcl_Command Tcl_CreateObjCommand(interp, cmdName, proc, clientData, deleteProc)

so it returns a Tcl_Command (which is a token for the command) and takes 5 arguments:

  • interp - the interpreter in which to create the new command
  • cmdName - the name of the new command (possibly in a specific namespace)
  • proc - the name of a C function to handle the command execution when called by a script
  • clientData - some data associated with the command, when a state needs to be taken care of (a file for example); this is typically used where a proc is used to create a whole family of commands, such as the instances of a kind of Tk widget.
  • deleteProc - a C function to call when the command is deleted from the interpreter (used for cleanup of the clientData) which may be NULL if no cleanup is needed.

A full example of the usage can be found in: Hello World as a C extension. Here is an example from the sqlite extension used to create the 'sqlite' command:

Tcl_CreateObjCommand(interp, "sqlite", (Tcl_ObjCmdProc*)DbMain, 0, 0);

Taking the same sqlite example, we can go a step further and look at how to handle clientData. When sqlite opens a database, a new command is created which handles SQL queries on that database file. The command must of course know this file and since you can have more databases open at one point, you don't want to mix them. That is what the clientData parameter can handle, it handles so-called 'state data'.

It is best to hold these clientData in a struct (thanks to schlenk and dgp for hints on this) and that could look like this:

typedef struct dbData {
    Tcl_Obj *fileObj;   /* the name of the file */
    Tcl_Channel chan;   /* the channel to the openend shape file */
} dbData;

So if we assume a command named 'mydb' that needs access to these data, we might create the command as follows (this is a fictitious example):

dbData *adb = (dbData*) ckalloc(sizeof(dbData));

/* given an already opened channel 'mychan', put the handle into the structure, so the 'mydb' command can access it */
adb->chan = mychan;

/* given a filename was given on the command line from script level, we can store it in the structure like this (assuming it came as the first argument): */
adb->fileObj = Tcl_DuplicateObj(objv[1]);
Tcl_IncrRefCount(adb->fileObj);

Tcl_CreateObjCommand(interp, "mydb", DbObjCmd, dbData, DbDeleteCmd);

Note here, that we used a Tcl_Obj for the name of the file. This is good style and an overall advantage. We could have used char *filename instead in the structure, but then our memory space would get lost after the completion of the function without adding extra code for memory management ourselves. Using a Tcl_Obj will do everything for us automatically (using reference counts -> see Tcl_Obj refCount HOWTO). To fill the filename into the object, we use Tcl_DuplicateObj. The only thing left to do is then to increease the reference count on the Tcl_Obj each time a new command is created from the same C function (we might have more than one file open at any time). When the file is closed, we must decrease the count again (thanks to Pat Thoyts, MJ, and RFox for explaining and helping here).

The clientData have now been used to "bind" the created command to the database file for which is was created via a structure holding the state data. The mydb command will now be handled by the DbObjCmd C function and the clientData are "dbData" in this case holding the data for an opened file, so the otherwise generic DbObjCmd function can operate on the right file. You can pass the clientData on to the created command as is, there is no need to cast this because Tcl won't cast it to something else and poke around inside (thanks to DKF for this clarification).

This is how the DbObjCmd function could look like:

static int
DbObjCmd(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *const objv[])
{
    dbData *bbb = (dbData*) clientData; /* Cast only required if command is implemented in C++ */
    /* do something intelligent here ... */
}

As you can see, the DbObjCmd not only gets passed our clientData but also some other information, but always matching the type Tcl_ObjCmdProc:

typedef int Tcl_ObjCmdProc(
        ClientData clientData,
        Tcl_Interp *interp,
        int objc,
        Tcl_Obj *const objv[]);

So this is a template for all those created commands that we might create and use.

Since we hold a reference to the state data, we must free this space eventually. This is why we also have defined a DbDeleteCmd above. This command is called before our 'mydb' command is deleted from the interpreter. ...

To be continued ...


Lars H, 2008-11-06: A bug/feature of this command is its behaviour for command names that are not fully qualified:

  • If the command name does not contain a namespace separator, then it is created in the :: namespace.
  • If the command name contains a namespace separator, then it is treated as being relative to the current namespace, creating the target namespace if it didn't already exist.

Since this is documented (although slightly inconsistent) behaviour for Tcl_CreateObjCommand, it is a feature rather than a bug, but it is typically not documented at the script level for commands that use Tcl_CreateObjCommand to create another command. This even includes core commands such as interp alias and image.

[L1 ] is a bug report on this issue.


See also