100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Tcl and C Extension Basics

Understand how to extend Tcl with compiled C code — writing custom commands, managing the Tcl_Obj type system, and building a loadable extension.

Practical Tcl/TkAdvanced11 min readJul 10, 2026
Analogies

Why Write a C Extension for Tcl

Tcl was designed from day one to be embeddable and extensible in C: the interpreter itself is a C library (libtcl), and new commands are added not by modifying the interpreter's source but by writing ordinary C functions that get registered at runtime with Tcl_CreateObjCommand, which is exactly how performance-critical or OS-level functionality (file I/O, networking, hardware access) has been added to Tcl and Tk themselves for decades.

🏏

Cricket analogy: Extending Tcl with a C command is like a franchise using a wildcard signing to add a bespoke skill — say, a dedicated death-bowling specialist — to the squad without rewriting the team's existing playing structure.

The Tcl_CreateObjCommand API

Tcl_CreateObjCommand(interp, "mycmd", MyCmdProc, clientData, deleteProc) registers a C function MyCmdProc to run whenever Tcl code calls mycmd; that function receives the interpreter, an argument count objc, and an array of Tcl_Obj* objv, and it signals success or failure by returning TCL_OK or TCL_ERROR while setting the interpreter's result via Tcl_SetObjResult.

🏏

Cricket analogy: Tcl_CreateObjCommand registering a callback for 'mycmd' is like the BCCI registering a specific player's central contract, so whenever that player's name is called upon for selection, a defined, pre-agreed function (their role) activates.

Working with Tcl_Obj and Reference Counting

Every value flowing through Tcl is a Tcl_Obj, a reference-counted structure holding both a string representation and, lazily, a typed internal representation (integer, list, dict, etc.); C code must call Tcl_IncrRefCount/Tcl_DecrRefCount correctly when storing or releasing a Tcl_Obj*, because letting the count drop to zero while another part of the code still holds a pointer to it is a classic use-after-free bug in Tcl extensions.

🏏

Cricket analogy: Tcl_Obj's reference counting is like a shared match ball being tracked by the umpire — everyone using it (bowler, fielder) must account for it, and only when no one needs it anymore does it get retired from play.

c
#include <tcl.h>

/* mycmd_double NUM -> returns NUM * 2 as a Tcl integer */
static int
DoubleCmd(ClientData clientData, Tcl_Interp *interp,
          int objc, Tcl_Obj *const objv[])
{
    if (objc != 2) {
        Tcl_WrongNumArgs(interp, 1, objv, "num");
        return TCL_ERROR;
    }

    long value;
    if (Tcl_GetLongFromObj(interp, objv[1], &value) != TCL_OK) {
        return TCL_ERROR;
    }

    Tcl_SetObjResult(interp, Tcl_NewLongObj(value * 2));
    return TCL_OK;
}

int
Mycmd_Init(Tcl_Interp *interp)
{
    if (Tcl_InitStubs(interp, "8.6", 0) == NULL) {
        return TCL_ERROR;
    }
    Tcl_CreateObjCommand(interp, "mycmd_double", DoubleCmd,
                          (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_PkgProvide(interp, "Mycmd", "1.0");
    return TCL_OK;
}

Building and Loading a Shared Library

Building a loadable extension means compiling against the Tcl headers and stub library with Tcl_InitStubs(interp, "8.6", 0) at the top of the _Init function — using the stubs mechanism instead of linking directly against libtcl.so lets a single compiled extension binary load correctly into any sufficiently new Tcl version at runtime, rather than requiring a rebuild for every point release.

🏏

Cricket analogy: Building against Tcl's stub library instead of a fixed libtcl version is like a bowler's action being certified once by the ICC and remaining legal across every subsequent series, rather than needing re-certification for every new tournament.

Extensions built without the stubs mechanism (linking directly against a specific libtcl8.6.so) will typically fail to load, or worse, crash unpredictably, against a different Tcl build (a different patch version, a different threading model, or a statically-linked tclsh); always use Tcl_InitStubs for anything you intend to distribute as a compiled .so/.dll rather than build alongside a fixed application.

Passing Data Between Tcl and C Safely

Passing complex data from Tcl to C safely means converting through the Tcl_Obj API rather than manipulating a Tcl_Obj's string representation directly: Tcl_ListObjGetElements extracts a list's elements as a Tcl_Obj* array without a lossy string round-trip, Tcl_GetIntFromObj/Tcl_GetDoubleFromObj validate and convert numeric arguments (returning TCL_ERROR with a proper Tcl error message on bad input instead of crashing), and results should be constructed with Tcl_NewListObj/Tcl_NewDictObj rather than hand-built strings that Tcl then has to reparse.

🏏

Cricket analogy: Using Tcl_ListObjGetElements instead of manually parsing a string is like a scorer reading directly from the official electronic scoring system's structured data instead of re-transcribing numbers off a hand-written paper scoresheet, avoiding transcription errors.

Because Tcl_Obj maintains both a string and a typed internal representation, converting the same object back and forth between types (say, treating a list as a string, then back as a list) can be cheap if Tcl caches the conversion, but repeatedly forcing string-to-typed conversions in a hot loop (like Tcl_GetIntFromObj on a freshly-created string object every iteration) is a common, avoidable source of extension slowness — profile before assuming your C extension is automatically faster than equivalent Tcl code.

  • New Tcl commands are added by registering ordinary C functions with Tcl_CreateObjCommand, without modifying the interpreter itself.
  • A command's C handler receives objc/objv and reports success or failure via TCL_OK/TCL_ERROR plus Tcl_SetObjResult.
  • Every Tcl value is a reference-counted Tcl_Obj; mismanaging Tcl_IncrRefCount/Tcl_DecrRefCount causes use-after-free bugs.
  • Tcl_InitStubs lets one compiled extension binary load correctly across multiple Tcl versions instead of requiring per-version rebuilds.
  • Data should move between Tcl and C through the Tcl_Obj API (Tcl_ListObjGetElements, Tcl_GetIntFromObj, etc.), not manual string parsing.
  • Results should be built with Tcl_NewListObj/Tcl_NewDictObj rather than hand-assembled strings Tcl then has to reparse.
  • C extensions aren't automatically faster than Tcl code; repeated string/typed conversions in hot loops can erase any performance benefit.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#TclTkStudyNotes#TclAndCExtensionBasics#Tcl#Extension#Write#CreateObjCommand#StudyNotes#SkillVeris#ExamPrep