Hello World as a C extension

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 Extension

 /*
  * 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) == NULL) {
         return TCL_ERROR;
     }
     /* changed this to check for an error - GPS */
     if (Tcl_PkgProvide(interp, "Hello", "1.0") == TCL_ERROR) {
         return TCL_ERROR;
     }
     Tcl_CreateObjCommand(interp, "hello", Hello_Cmd, NULL, NULL);
     return TCL_OK;
 }

Explanatory Notes

DKF: 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".

Advanced note on the arguments to Hello_Cmd (taken from the chat and using dgp's words ...)

This only applies, if you need to deal with arguments to your command so this simple Hello world command does not need the following knowledge.

When Hello_Cmd is called, the objv array holds objc pointers to Tcl_Obj structs. At that point you could pass any of those pointers to Tcl_GetString() and get back a pointer to the first element of an array of bytes that are a string in Tcl's internal encoding terminated by a NULL byte. If you pass one of those pointers to some other routine, it could happen that the Tcl_Obj might get free'd by that routine. So to prevent that you should Tcl_IncrRefCount(objv[i]) on any argument you need to be sure lives on after being passed to something and balance that with a Tcl_DecrRefCount(objv[i]) when you don't need the insurance any more (Tcl_GetString will not free the Tcl_Obj). Just because the objv[i] isn't freed, you can't conclude that the pointer you get back from Tcl_GetString() might not be freed. Their lifetimes are not the same. So, for any pointer you get back from Tcl_GetString(), you ought to make a copy of that string before you do any thing else, assuming you want to keep it around for a while (see also Tcl_Obj refCount HOWTO).

Building the Extension

Building C code is a difficult thing to describe, because so many compilers do things differently. In the notes below, developers have begun to add examples of the command line arguments one would use for various compilers.

One of the things that you _might_ be able to use to help you is a file called tclConfig.sh . This file is one that is installed into tcl's primary library directory, and contains a series of shell variable assignments that correspond to flags used to compile the original tclsh interpreter. The closer you match those flags, the better your chances are that your extension is going to work.

[Please replace this comment with details as to how to make use of the compile variables!]

To compile the extension above, you should copy the above code into a file called hello.c. To compile this code will require that you provide to the compiler the location of the tcl.h header, at the very least. 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.

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

More on Building an extension for Tcl under Mac OS X and Building Tcl DLL's for Windows and Building the Hello C Extension using Visual Studio 2010 express:

To load, remember to do:

 tclsh8.4
 % load ./libhello[info sharedlibextension]
 % hello
 Hello, World!

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

After running a compile step similar to the above, 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?]

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

When the extension is written in C++, make sure to wrap all functions that are called by Tcl in an extern "C" environment:

 extern "C" {

 static int 
 Hello_Cmd(...)
 {
     ...
 }

 int DLLEXPORT
 Hello_Init(...)
 {
     ...
 }
 } /* extern "C" */

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 */

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

EG There is no need to use Tcl_CreateNamespace() before using Tcl_CreateObjCommand(). The later creates namespaces as needed, which is counterintuitive given the (opposite) behaviour of proc.

HolgerJ 2015-06-07 I was successful using the code above with Hello_Cmd() from the top and Hello_Init() right above this place here and the following settings:

  $ export TCLINC=/usr/include/tcl8.6
  $ export TCLLIB=/usr/lib/x86_64-linux-gnu
  $ gcc -fPIC -shared -o libhello.so -DUSE_TCL_STUBS -I$TCLINC hello.c -L$TCLLIB -ltclstub8.6
  $ tclsh
  % load ./libhello.so
  % ::hello::hello
  Hello, World!
  %

My machine is a Ubuntu 15.04 64-bit.

Creating a Package

NEM As this page has become somewhat more comprehensive than I originally planned (and a good thing too), it seems appropriate to add the next step: installing your C code as a package. Once you have your dynamic library, this is really quite simple. Firstly, you need to create a pkgIndex.tcl file (note the exact spelling), with instructions telling Tcl how it can load your package. The basic template looks something like this:

 # pkgIndex.tcl -- tells Tcl how to load my package.
 package ifneeded "Hello" 1.0 \
     [list load [file join $dir libhello[info sharedlibextension]]]

All this says is that when the "Hello" package, version 1.0, is required, then it can be found by loading the library libhello.so (or libhello.dll etc) from the directory where this pkgIndex.tcl file was found. Note that the version number in the pkgIndex.tcl file should exactly match that found in the Tcl_PkgProvide call in your C code.

You can then install this file along with your dynamic library in a directory where tcl can find it -- that is any of the directories specified in the auto_path variable, and any sub-directories. On UNIX systems this typically includes /usr/local/lib, on Windows it will likely include C:/Tcl/lib or C:/Program Files/Tcl/lib (perhaps localised), and on Mac OS X it likely includes /Library/Tcl. So, assuming we are on a UNIX machine and want to install into /usr/local/lib, we would create a directory such as:

 $ mkdir /usr/local/lib/hello1.0
 $ cp pkgIndex.tcl libhello.so /usr/local/lib/hello1.0/
 $ tclsh8.4
 % package require Hello 1.0
 1.0
 % hello
 Hello, World!

Use "hello::hello" if using the namespace version.


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


RS 2007-10-15: Another Windows example (namespacing avoided), using tcltcc:

 ~ $ tclsh
 % package require tcc
 0.2
 % tcc::dll hello
 hello
 % hello cproc hello {} char* {return "hello, world!";}
 % hello write -file hello.dll

If this isn't simple, what is? :^) Just to show that it works:

 % hello
 wrong # args: should be "hello cmd ..."

Oops.. that was still the compiler named hello

 % load hello.dll
 % hello
 hello, world!

Q.E.D.


See sampleextension for an example of a Tcl extension that also demonstrates the TEA Tcl Extension Architecture...


mghello


Can someone PLEASE provide an example of compiling & linking this example for AIX? I don't have gcc on my AIX box at work so I need an example using IBM's cc/xlc compiler. Actually, compiling seems to be quite straightforward. It's the linking/shared library build step that's giving me trouble. AIX's multitude of compiler and linker options are a source of limitless confusion. I appreciate any help.

SL I tried:

 xlc -c hello.cxx -o hello.o -I$TCLINCL -DUSE_TCL_STUBS
 xlc -qmkshrobj hello.o -L$TCLLIB -ltclstub8.4 -o hello.so

but get an error because of missing exports. Change

int DLLEXPORT

to

EXTERN int DLLEXPORT

and it works. From what I know, the DLLEXPORT is not necessary but the EXTERN should be present for all exported functions (at least [L1 ] page 740 advise to do so).

See Also

Extending Tcl

pratiktamakuwala - 2017-11-28 16:54:59

I have a .so(for example abc.so) file created using swig in unix which i am abe to load in the tcl program using 'load abc.so'. I plan to run the tcl program in windows, therefore need to create a corresponding abc.dll file which can be loaded similarly. Kindy guide the procedure to achieve this. Thanks!