[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** ======c /* * hello.c -- A minimal Tcl C extension. */ #include 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 page]s, 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. ---- ***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: ======c 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 ' 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: ======c #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.) ***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: ======none 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 [http://wiki.tcl.tk/8449] page 740 advise to do so). ---- **See Also** * [Writing Extensions] <> Tutorial | Foreign Interfaces