Tcl_CreateObjCommand(3)

πŸ’– NAME πŸ’–

okay so bestie, welcome to the Tcl Library Procedures page for Tcl_CreateObjCommand(3) β€” yes the title and the manual name are literally the same thing, iconic of them honestly. This whole page is about how you, a C programmer with main character energy, get to implement new commands in C. πŸ‘‘

The squad of functions you're about to meet (they all live on this page, they all pull up together):

  • Tcl_CreateObjCommand 🌸
  • Tcl_CreateObjCommand2 🌸
  • Tcl_DeleteCommand πŸ—‘οΈ
  • Tcl_DeleteCommandFromToken πŸ—‘οΈ
  • Tcl_GetCommandInfo πŸ”
  • Tcl_GetCommandInfoFromToken πŸ”
  • Tcl_SetCommandInfo ✏️
  • Tcl_SetCommandInfoFromToken ✏️
  • Tcl_GetCommandName 🏷️
  • Tcl_GetCommandFullName 🏷️
  • Tcl_GetCommandFromObj 🎯

They do the whole lifecycle: create, delete, inspect, mutate, name, and fetch. It's giving full command-management ecosystem. periodt.


✨ SYNOPSIS ✨

okay so first you gotta include the header, obviously, no cap:

#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)

like, look at her. all those signatures in a row. πŸ’…


πŸŽ€ ARGUMENTS πŸŽ€

okay so let's break down every single parameter, because every single one matters, fr fr:

  • Tcl_Interp *interp (in) β€” the interpreter you're creating a new command in, or the one that already contains the command you're messing with. your home base. 🏠
  • const char *cmdName (in) β€” the name of the command. that's it, that's the whole job. 🌸
  • Tcl_ObjCmdProc *proc (in) β€” this is the implementation of your brand new command: proc will get called every single time cmdName is invoked as a command. she's the moment. the way that she shows up whenever you call her? obsessed. πŸ’–
  • Tcl_ObjCmdProc2 *proc2 (in) β€” same energy as above: this is the implementation of the new command, and proc2 will be called whenever cmdName is invoked as a command. ✨
  • void *clientData (in) β€” arbitrary one-word value to pass to proc and deleteProc. it's like a little gift you hand over. 🎁
  • Tcl_CmdDeleteProc *deleteProc (in) β€” the procedure to call before cmdName is deleted from the interpreter; it's your window for command-specific cleanup. and if it's NULL, then no procedure is called before the command is deleted. simple as that. 🧹
  • Tcl_Command token (in) β€” the token for a command, returned by a previous call to Tcl_CreateObjCommand. ⚠️ heads up bestie: the command must not have been deleted. so like, keep track of your tokens, okay?
  • Tcl_CmdInfo *infoPtr (in/out) β€” pointer to a structure containing various information about a Tcl command. she reads and writes. versatile. πŸ‘‘
  • Tcl_Obj *objPtr (in) β€” a value containing the name of a Tcl command. 🎯
  • const char *typeName (in) β€” indicates the name of the type of command implementation associated with a particular proc, or NULL to break the association. (yes this one showed up in ARGUMENTS without being in the synopsis, she's mysterious like that πŸ¦‹)

✨ DESCRIPTION ✨

okay so, Tcl_CreateObjCommand is the girl that makes a brand new command inside interp, and she pairs it up with the procedure proc so that any time somebody yells name as a Tcl command (like, say, through a call to Tcl_EvalObjEx) the Tcl interpreter is gonna go call proc to handle it. πŸ’…

And bestie, she's not shy β€” Tcl_CreateObjCommand deletes any command already hanging around under the name name in that interpreter (but hold on, there's an exception down below where the existing command does not get deleted, don't @ me yet). She hands you back a token you can use to refer to that command in later calls to Tcl_GetCommandName. If name has any :: namespace qualifiers in it, the command gets added to that namespace; otherwise it just goes to the global namespace, nbd. And important: if you call Tcl_CreateObjCommand on an interpreter that's literally in the process of being deleted, she does not create a new command and returns NULL. 😭

proc needs args and a result matching 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 params are gonna be copies of the clientData and interp args you handed to Tcl_CreateObjCommand. Usually clientData points at some application-specific data structure that says what to do when the command procedure runs. objc and objv describe the arguments to the command β€” objc is the number of argument values (command name included πŸ’–), and objv gives you the values of the arguments. The objv array holds objc values pointing at the argument values. And unlike argv[argv] used in a string-based command procedure, objv[objc] will not contain NULL. periodt.


also, heads 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 (like, for example, objv[2] = NULL) because that will cause memory to be lost and the runtime stack to be corrupted. πŸ’€ The const in the declaration of objv will make ANSI-compliant compilers report any such attempted assignment as an error, so honestly the compiler is doing you a favour. However β€” and this is the slay part β€” it is acceptable to modify the internal representation of any individual value argument. For instance, the user may call Tcl_GetIntFromObj on objv[2] to grab 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. πŸ¦‹

proc must return an integer code that's one of TCL_OK, TCL_ERROR, TCL_RETURN, TCL_BREAK, or TCL_CONTINUE. Peep the return man page for what those codes actually mean and for how extended values work for an extension's private use. Most normal commands are only ever gonna return TCL_OK or TCL_ERROR, fr fr.

Plus, if proc needs to return a non-empty result, it can call Tcl_SetObjResult to set the interpreter's result. If the return code is TCL_OK that gives you the result of the command, and if it's TCL_ERROR that gives you an error message. Before it invokes a command procedure, Tcl_EvalObjEx sets the interpreter's result to point at a value representing an empty string β€” so simple commands can return an empty result by literally doing nothing at all. ✨ iconic behaviour honestly.

The contents of the objv array belong to Tcl and are not guaranteed to persist once proc returns β€” so proc should not modify them. Call Tcl_SetObjResult if you want to return something from the objv array. 🫢


Ordinarily, Tcl_CreateObjCommand deletes any existing command name already associated with the interpreter. BUT 🚨 if that existing command was created by a previous call to Tcl_CreateCommand, then Tcl_CreateObjCommand does not delete the command β€” instead she 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, bestie, we respect our elders. πŸ‘‘

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

typedef void Tcl_CmdDeleteProc(
        void *clientData);

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

Tcl_CreateObjCommand2 does the same 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[]);

Tcl_DeleteCommand deletes a command from a command interpreter. Once that call completes, any attempt to invoke cmdName in interp is gonna result in errors. If cmdName isn't 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. And if name contains any :: namespace qualifiers, the command gets deleted from the specified namespace.

Given a token returned by Tcl_CreateObjCommand, Tcl_DeleteCommandFromToken deletes the command from a command interpreter. She'll delete a command even if that command has been renamed β€” she's not fooled by a glow-up. πŸ’… 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.

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, she returns 0. Otherwise she places info about the command in the Tcl_CmdInfo structure pointed to by infoPtr and returns 1. A Tcl_CmdInfo structure has these fields, no cap:

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;

okay let's break down the fields because they're giving ✨lore✨:

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; and it's 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, that's the procedure passed to it; otherwise, it's 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. And the field namespacePtr holds a pointer to the Tcl_Namespace that contains the command. πŸŽ€

Tcl_GetCommandInfoFromToken is identical to Tcl_GetCommandInfo except 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.

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 that command doesn't 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 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.

⚠️ note, bestie: 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 if that's the vibe you're going for.

Tcl_GetCommandName gives you a mechanism for tracking commands that have been renamed. Given a token returned by Tcl_CreateObjCommand back 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 β€” she keeps up, she's not stuck in the past. πŸ¦‹ 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 keep its value as long as the command isn't deleted or renamed; so callers should copy the string if they need to hold onto it for a long time. ✨

Tcl_GetCommandFullName produces the fully qualified name of a command from a command token. The name β€” including all namespace prefixes β€” gets 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 isn't found. 🌈


πŸ’– REFERENCE COUNT MANAGEMENT πŸ’–

okay bestie, this part is lowkey the most important so like, pay attention fr fr 🫢

so when the proc you passed to Tcl_CreateObjCommand finally gets called, the values sitting in its objv argument are gonna have a reference count of at least 1. and that guaranteed reference? it's coming from the Tcl evaluation stack, periodt. πŸ’…

do NOT call Tcl_DecrRefCount on any of those values unless you called Tcl_IncrRefCount on them first. like, don't even think about it bestie, that's how you get the crash of a lifetime 😭

also!! when the proc gets called, the interpreter result is guaranteed to be an empty string value with a reference count of 1. no cap, that's just the vibe going in. ✨


and a couple more refcount tea drops for you πŸ“

  • Tcl_GetCommandFullName β€” does not modify the reference count of its objPtr argument. but it does require that the object be unshared. so like, if it's shared, that's a you problem, not hers πŸ™ˆ
  • Tcl_GetCommandFromObj β€” does not modify the reference count of its objPtr argument either. she's just reading. no touching, no changing, she's a look-don't-touch icon πŸ‘‘

✨ SEE ALSO ✨

go read these too, they're giving main character energy 🌈


πŸŽ€ KEYWORDS πŸŽ€

the little search terms that got you here, iykyk πŸ‘‘

bind, command, create, delete, namespace, value


and that's the whole page bestie!! you ate that πŸ’…βœ¨

this gorgeous lil manual page is from the Tcl source, version 8.0, and it's the one and only Tcl_CreateObjCommand(3) 🌸 mwah, go write some commands, you've got this πŸ’–πŸ¦‹

πŸ“¦ where this page comes from