Tcl_CreateObjCommand(3tcl)

πŸ“¦
version 9.0.4 Β· 2 versions
, in debian trixie, ubuntu questing, ubuntu resolute, ubuntu stonking

✨ NAME ✨

okay bestie, so this is the Tcl Library Procedures corner of the world (that's the 3tcl manual, hi πŸ‘‹), and today we're learning how to implement new commands in C. yes fr, actual C. you got this πŸ’…

the whole girl squad of functions showing up in this page is:

  • Tcl_CreateObjCommand
  • Tcl_CreateObjCommand2
  • Tcl_DeleteCommand
  • Tcl_DeleteCommandFromToken
  • Tcl_GetCommandInfo
  • Tcl_GetCommandInfoFromToken
  • Tcl_SetCommandInfo
  • Tcl_SetCommandInfoFromToken
  • Tcl_GetCommandName
  • Tcl_GetCommandFullName
  • Tcl_GetCommandFromObj

all of them, together, doing the "make a Tcl command out of C code" thing. iconic behavior honestly πŸ‘‘


🌸 SYNOPSIS 🌸

like here's the whole vibe check, verbatim, no notes:

#include <tcl.h>

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

Tcl_Command
Tcl_CreateObjCommand2(interp, cmdName, proc2, clientData, deleteProc)

int
Tcl_DeleteCommand(interp, cmdName)

int
Tcl_DeleteCommandFromToken(interp, token)

int
Tcl_GetCommandInfo(interp, cmdName, infoPtr)

int
Tcl_SetCommandInfo(interp, cmdName, infoPtr)

int
Tcl_GetCommandInfoFromToken(token, infoPtr)

int
Tcl_SetCommandInfoFromToken(token, infoPtr)

const char *
Tcl_GetCommandName(interp, token)

Tcl_GetCommandFullName(interp, token, objPtr)

Tcl_Command
Tcl_GetCommandFromObj(interp, objPtr)

πŸŽ€ ARGUMENTS πŸŽ€

kay so let's go through who's who, one by one, no skipping, we don't do that here:

  • Tcl_Interp *interp (in) β€” the interpreter you're making a shiny new command inside of, or the one that's already holding a command you care about.
  • const char *cmdName (in) β€” the name of the command. that's it. that's the girl. πŸ’–
  • Tcl_ObjCmdProc *proc (in) β€” your implementation of the new command: proc gets called every single time cmdName is invoked as a command. she's the moment.
  • Tcl_ObjCmdProc2 *proc2 (in) β€” also your implementation of the new command: proc2 gets called whenever cmdName is invoked as a command. yes she's a different girlie, don't mix them up 🫢
  • void *clientData (in) β€” some arbitrary one-word value that gets passed along to proc and deleteProc. no cap, it can be whatever you want.
  • Tcl_CmdDeleteProc *deleteProc (in) β€” the procedure that gets called before cmdName is deleted from the interpreter; this is your moment for command-specific cleanup. and if it's NULL, then literally no procedure is called before the command gets deleted. simple as that ✨
  • Tcl_Command token (in) β€” the token for a command, which you get back from a previous call to Tcl_CreateObjCommand. ⚠️ and this is important bestie: the command must not have been deleted. like, do not.
  • Tcl_CmdInfo *infoPtr (in/out) β€” a pointer to a structure holding all kinds of information about a Tcl command. she's got range, in AND out πŸ’…
  • Tcl_Obj *objPtr (in) β€” a value containing the name of a Tcl command.
  • const char *typeName (in) β€” tells you the name of the type of command implementation associated with a particular proc. or, plot twist, NULL to break the association πŸ’€

πŸ’– DESCRIPTION πŸ’–

okay bestie, deep breath, this is the main character section ✨

Tcl_CreateObjCommand defines a brand-new command in interp and pairs it up with the procedure proc, so that every time name gets invoked as a Tcl command (like, say, through a call to Tcl_EvalObjEx), the Tcl interpreter is gonna call proc to handle it. that's the whole vibe. 🫢

Tcl_CreateObjCommand deletes any command already chilling under that name in the interpreter (but hold on β€” there's an exception below where the existing command does not get deleted, so don't come for me). it returns a token you can use to refer to the command later in calls to Tcl_GetCommandName. if name has any :: namespace qualifiers in it, the command gets added to that namespace; otherwise it lands in the global namespace. and if you call Tcl_CreateObjCommand on an interpreter that's currently in the process of being deleted? it does not create a new command and just returns NULL. tragic but true. πŸ’€

proc should have arguments and a result that match the type Tcl_ObjCmdProc:

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

when proc gets invoked, the clientData and interp parameters are copies of the clientData and interp arguments you handed to Tcl_CreateObjCommand. typically clientData points at some application-specific data structure that describes what to do when the command procedure is invoked. objc and objv describe the arguments to the command β€” objc gives the number of argument values (including the command name, yes she counts herself πŸ’…) and objv gives the values of those arguments. the objv array will contain objc values, pointing to the argument values. and unlike argv[argv] used in a string-based command procedure, objv[objc] will not contain NULL. no sneaky sentinel, we're not doing that here.


🚨 the rules, bestie, listen up 🚨

when proc is invoked, it must not modify the contents of the objv array by assigning new pointer values to any element of the array (for example, objv[2] = NULL) β€” because that will cause memory to be lost and the runtime stack to be corrupted. that's not a vibe, that's a crash. 😭 the const in the declaration of objv will make ANSI-compliant compilers report any such attempted assignment as an error, so the compiler is literally your bodyguard here.

however! it is acceptable to modify the internal representation of any individual value argument. for instance, you may call Tcl_GetIntFromObj on objv[2] to get the integer representation of that value; that call may change the type of the value that objv[2] points at, but it will not change where objv[2] points. same address, new outfit. ✨

proc must return an integer code that is either TCL_OK, TCL_ERROR, TCL_RETURN, TCL_BREAK, or TCL_CONTINUE. check the return man page for details on what these codes mean, and for the use of extended values for an extension's private use. most normal commands will only return TCL_OK or TCL_ERROR β€” keep it simple, queen. πŸ‘‘

and if proc needs to return a non-empty result, it can call Tcl_SetObjResult to set the interpreter's result. with a TCL_OK return code that gives you the result of the command; with TCL_ERROR it gives you an error message. before invoking a command procedure, Tcl_EvalObjEx sets the interpreter's result to point to a value representing an empty string β€” so simple commands can return an empty result by literally doing nothing at all. the dream. πŸ™ˆ

the contents of the objv array belong to Tcl and are not guaranteed to persist once proc returns: proc should not modify them. call Tcl_SetObjResult if you actually want to return something from the objv array.


πŸ“Ž the backwards-compatibility plot twist πŸ“Ž

ordinarily, Tcl_CreateObjCommand deletes any existing command name already associated with the interpreter. however β€” if the existing command was created by a previous call to Tcl_CreateCommand, then Tcl_CreateObjCommand does not delete the command, and instead arranges for the Tcl interpreter to call the Tcl_ObjCmdProc proc in the future. the old string-based Tcl_CmdProc associated with the command is retained, and its address can be obtained by subsequent Tcl_GetCommandInfo calls. this is done for backwards compatibility. it's giving legacy support, and honestly? iconic for it. πŸŽ€

DeleteProc will be invoked when (if) name is deleted. this can happen through a call to Tcl_DeleteCommand, Tcl_DeleteCommandFromToken, or Tcl_DeleteInterp, or by replacing name in another call to Tcl_CreateObjCommand. DeleteProc is invoked before the command is deleted, and gives the application a chance to release any structures associated with the command. DeleteProc should have arguments and a result that match the type Tcl_CmdDeleteProc:

typedef void Tcl_CmdDeleteProc(
        void *clientData);

the clientData argument will be the same as the clientData argument you passed to Tcl_CreateObjCommand.

Tcl_CreateObjCommand2 does the same thing as Tcl_CreateObjCommand, except its proc2 argument is of type Tcl_ObjCmdProc2.

typedef int Tcl_ObjCmdProc2(
        void *clientData,
        Tcl_Interp *interp,
        Tcl_Size objc,
        Tcl_Obj *const objv[]);

πŸ—‘οΈ deleting, bestie πŸ—‘οΈ

Tcl_DeleteCommand deletes a command from a command interpreter. once the call completes, any attempt to invoke cmdName in interp will result in errors. if cmdName is not bound as a command in interp, then Tcl_DeleteCommand does nothing and returns -1; otherwise it returns 0. there are no restrictions on cmdName: it may refer to a built-in command, an application-specific command, or a Tcl procedure. if name contains any :: namespace qualifiers, the command is deleted from the specified namespace.

given a token returned by Tcl_CreateObjCommand, Tcl_DeleteCommandFromToken deletes the command from a command interpreter. it will delete a command even if that command has been renamed. once the call completes, attempts to invoke the command in interp will result in errors. if the command corresponding to token has already been deleted from interp, then Tcl_DeleteCommand does nothing and returns -1; otherwise it returns 0.


πŸ” peeking behind the curtain: Tcl_GetCommandInfo πŸ”

Tcl_GetCommandInfo checks whether its cmdName argument exists as a command in interp. cmdName may include :: namespace qualifiers to identify a command in a particular namespace. if the command isn't found, it returns 0. otherwise it places info about the command into the Tcl_CmdInfo structure pointed to by infoPtr and returns 1. a Tcl_CmdInfo structure has these fields:

typedef struct {
    int isNativeObjectProc;
    Tcl_ObjCmdProc *objProc;
    void *objClientData;
    Tcl_CmdProc *proc;
    void *clientData;
    Tcl_CmdDeleteProc *deleteProc;
    void *deleteData;
    Tcl_Namespace *namespacePtr;
    Tcl_ObjCmdProc2 *objProc2;
    void *objClientData2;
} Tcl_CmdInfo;

the isNativeObjectProc field has the value 2 if Tcl_CreateObjCommand2 was called to register the command; it has the value 1 if Tcl_CreateObjCommand was called to register the command; it is 0 if only Tcl_CreateCommand was called. it lets a program figure out whether it's faster to call objProc2, objProc or proc: objProc2/objProc is normally faster if isNativeObjectProc has the value 2; objProc/objProc is normally faster if isNativeObjectProc has the value 1.

the fields objProc and objClientData have the same meaning as the proc and clientData arguments to Tcl_CreateObjCommand; they hold info about the value-based command procedure that the Tcl interpreter calls to implement the command. the fields proc and clientData hold info about the string-based command procedure that implements the command. if Tcl_CreateCommand was called for this command, this is the procedure passed to it; otherwise, this is a compatibility procedure registered by Tcl_CreateObjCommand that simply calls the command's value-based procedure after converting its string arguments to Tcl values. the field deleteData is the clientData value to pass to deleteProc; it's normally the same as clientData but may be set independently using the Tcl_SetCommandInfo procedure. the field namespacePtr holds a pointer to the Tcl_Namespace that contains the command.

Tcl_GetCommandInfoFromToken is identical to Tcl_GetCommandInfo except that it uses a command token returned from Tcl_CreateObjCommand in place of the command name. if the token parameter is NULL, it returns 0; otherwise, it returns 1 and fills in the structure designated by infoPtr.


✏️ setting the info: Tcl_SetCommandInfo ✏️

Tcl_SetCommandInfo is used to modify the procedures and clientData values associated with a command. its cmdName argument is the name of a command in interp. cmdName may include :: namespace qualifiers to identify a command in a particular namespace. if this command does not exist, then Tcl_SetCommandInfo returns 0. otherwise, it copies the information from *infoPtr to Tcl's internal structure for the command and returns 1.

Tcl_SetCommandInfoFromToken is identical to Tcl_SetCommandInfo except that it takes a command token as returned by Tcl_CreateObjCommand instead of the command name. if the token parameter is NULL, it returns 0. otherwise, it copies the information from *infoPtr to Tcl's internal structure for the command and returns 1.

heads up bestie: note that Tcl_SetCommandInfo and Tcl_SetCommandInfoFromToken both allow the clientData for a command's deletion procedure to be given a different value than the clientData for its command procedure. also note that neither Tcl_SetCommandInfo nor Tcl_SetCommandInfoFromToken will change a command's namespace. use Tcl_Eval to call the rename command to do that. ✨

🏷️ names, names, names 🏷️

Tcl_GetCommandName gives you a way to track commands that have been renamed. given a token returned by Tcl_CreateObjCommand when the command was created, Tcl_GetCommandName returns the string name of the command. if the command has been renamed since it was created, then Tcl_GetCommandName returns the current name. this name does not include any :: namespace qualifiers. the command corresponding to token must not have been deleted. the string returned by Tcl_GetCommandName lives in dynamic memory owned by Tcl and is only guaranteed to retain its value as long as the command is not deleted or renamed β€” so callers should copy the string if they need to keep it around for a long time. 🫢

Tcl_GetCommandFullName produces the fully qualified name of a command from a command token. the name, including all namespace prefixes, is appended to the value specified by objPtr.

Tcl_GetCommandFromObj returns a token for the command specified by the name in a Tcl_Obj. the command name is resolved relative to the current namespace. returns NULL if the command is not found. πŸ’€

🫢 REFERENCE COUNT MANAGEMENT 🫢

okay bestie, this part is important, like, do-not-skip-it important, because refcounts will absolutely humble you if you play with them wrong. πŸ’…

When the proc you passed to Tcl_CreateObjCommand gets called, the values sitting in its objv argument are gonna have a reference count of at least 1, and that guaranteed reference is coming straight from the Tcl evaluation stack. so basically they're already being held onto for you. 🫢

⚠️ Do not call Tcl_DecrRefCount on any of those values unless you call Tcl_IncrRefCount on them first. like, fr fr, don't just start decrementing, that's how you get chaos. 😭

Also, when the proc is called, the interpreter result is guaranteed to be an empty string value with a reference count of 1. so that's just handed to you, pre-loved, refcount included. ✨


  • Tcl_GetCommandFullName β€” does not modify the reference count of its objPtr argument, but it does require that the object be unshared. so no sharing, bestie, it's giving exclusive. πŸ’…
  • Tcl_GetCommandFromObj β€” does not modify the reference count of its objPtr argument either. it only reads. that's it. no touchy, just looky. πŸ‘€

🌈 SEE ALSO 🌈


✨ KEYWORDS ✨

bind, command, create, delete, namespace, value


and that's the whole page, babe. πŸ’– this one's from Tcl 8.0, filed under Tcl_CreateObjCommand(3tcl), and it's dated way back with the rest of the 8.0 era. go forth and manage those refcounts responsibly, mwah. πŸŽ€πŸ¦‹

πŸ“¦ where this page comes from