Version 38 of Hello World as a C extension

Updated 2006-11-11 11:41:12 by lwv

NEM A question on the chat was how to do a simple "Hello, World!" type command or extension, using Tcl's C API. This is dead easy, and is probably a good place to start for newbies. So here is the code.

 /*
  * hello.c -- A minimal Tcl C extension.
  */
 #include <tcl.h>

 static int 
 Hello_Cmd(ClientData cdata, Tcl_Interp *interp, int objc,  Tcl_Obj * CONST objv[])
 {
     Tcl_SetObjResult(interp, Tcl_NewStringObj("Hello, World!", -1));
     return TCL_OK;
 }

 /*
  * Hello_Init -- Called when Tcl loads your extension.
  */
 int DLLEXPORT
 Hello_Init(Tcl_Interp *interp)
 {
     if (Tcl_InitStubs(interp, TCL_VERSION, 0) == 0L) {
         return TCL_ERROR;
     }
     Tcl_CreateObjCommand(interp, "hello", Hello_Cmd, NULL, NULL);
     Tcl_PkgProvide(interp, "Hello", "1.0");
     return TCL_OK;
 }

DKF provides some explanatory commentary for newcomers...

In this file, the entry point (which Tcl discovers through dynamic library magic) is called Hello_Init, and that is responsible for connecting the extension up to the Tcl interpreter. It does this by first calling Tcl_InitStubs (which allows it to call other parts of the Tcl C API), then it creates the guts of the extension (here using Tcl_CreateObjCommand to register Hello_Cmd with the name "hello"). Following that, it then formally provides the Hello package (allowing for software versioning control) and returns TCL_OK to indicate that the setup succeeded.

Inside Hello_Cmd, we ignore the arguments (fine for simple commands), and instead just set the result to a new string containing "Hello, World!". The -1 just means "take all characters" in a C strlen() sense; positive numbers are used when you know the length of the string, but it's usually easier with literals to use the auto-length feature. Finally, the command implementation returns TCL_OK to say "this was a successful run of the command".

Building the Extension

To compile the extension above you should copy the code into a file called hello.c. Then you need to check the location of your version of Tcl. We need to know the directory that contains the tcl.h and the libtclstub file (tclstubNN.lib on windows). The simplest method is to launch tclsh and examine the value of tcl_library. Below substitute TCLINC for the path that contains tcl.h and TCLLIB for the path that contains the library.

You should end up with a shared library that you can [load] into tclsh, and then call the "hello" command. Lots of details left out - consult the man pages, books and more extensive docs elsewhere. [Could we at least fill in a skeleton of what kind of details have been left out, so that one knows what to look for elsewhere?]

Also see the sampleextension. Oh, and adapt the compiler line to your OS/compiler combination.

Unix:

 gcc -shared -o libhello.so -DUSE_TCL_STUBS -I$TCLINC hello.c -L$TCLLIB -ltclstub8.4

Windows (using MSVC)

 cl -nologo -W3 -O2 -MD -DUSE_TCL_STUBS -I$TCLINC -c hello.c
 link -nologo -release -dll -out:hello.dll hello.obj -libpath:$TCLLIB tclstub84.lib

Windows (using mingw gcc)

 gcc -shared -o hello.dll -DUSE_TCL_STUBS -I$TCLINC -L$TCLLIB -ltclstub84

MaxOSX:

 gcc -dynamiclib -DUSE_TCL_STUBS hello.c -L/Library/Frameworks/Tcl.framework -ltclstub8.4 -o libhello.dylib

To load, remember to do:

 tclsh8.4
 % load ./libhello[info sharedlibextension]

so that Tcl finds it ([load] doesn't look in the current dir unless told to).


Using namespaces

TR - Many extensions nowadays create their commands in a namespace. To do the above example using namespaces, you only need two more lines in the code:

 Hello_Init(Tcl_Interp *interp)
 {
        Tcl_Namespace *nsPtr; /* pointer to hold our own new namespace */
        Tcl_Namespace *nsPtr; /* pointer to hold our own new namespace */
      /* Note that I use '== NULL' here instead of '== 0L', since that seems more correct */
        if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) {
                return TCL_ERROR;
        }
        }
      /* create the namespace named 'hello' */
        nsPtr = Tcl_CreateNamespace(interp, "hello", NULL, NULL);
        if (nsPtr == NULL) {
            return TCL_ERROR;
        }
        }
      /* just prepend the namespace to the name of the command.
         Tcl will now create the 'hello' command in the 'hello'
         namespace so it can be called as 'hello::hello' */
        Tcl_CreateObjCommand(interp, "hello::hello", Hello_Cmd, NULL, NULL);
        Tcl_PkgProvide(interp, "Hello", "1.0");
        return TCL_OK;
 }

Note that compiling the example with the Tcl_CreateNamespace() function will give you a warning using Tcl 8.4 (at least until 8.4.12) and no warning using Tcl 8.5. This is because Tcl_CreateNamespace is an internal function in 8.4 and public in 8.5. So compiling with Tcl 8.4 you should add the line '#include <tclInt.h>' to the C source if you want to avoid this warning. But the code works regardless of this directive.

CLN - In my extension, I put the namespace in a #define so I have:

  #define NS "hello"
  ...
  nsPtr = Tcl_CreateNamespace(interp, NS, NULL, NULL);
  ...
  Tcl_CreateObjCommand(interp, NS "::hello", Hello_Cmd, NULL, NULL);

(Note that there's no operator between the NS and the "::hello" for Tcl_CreateObjCommand(), C very nicely catenates adjacent literal strings.)


There is also a sample with Windows build instructions at Building Tcl DLL's for Windows


[Category Tutorial|Category Foreign Interfaces]