Version 25 of Extension Stubs Tables

Updated 2012-03-11 01:04:07 by pooryorick

[de] The good stuff by Kevin Kenny. Other stuff by me.

tcl extensions in C are described at Writing Extensions, with the tcl sampleExtension as an example.

This page explains how to build an extension that exports a Stubs table for use at the C level by other extensions.

There are two steps

  • build the extension such that it exports a Stubs table a client a client
  • extension refers to the exported stubs table of the first extension

examples

  • Tcl itself exports a stubs table
  • Trf
  • Snack
    • also includes a simple example of using the snack API from another Tcl extension
  • [AK] TclXml/TclDOM/TclXSLT
    • show how an extension uses the Tcl stubs table
    • provides its own stub tables
    • uses the stub table of another extension
    • both imports and exports a stubs table (TclDOM/libxml2 and tDOM

How to export a stubs table

Include the following four additional files in your extension:

  • extension.h
    • includes all tyedef's and macros that are part API of your extension
    • at the end of the file, make sure to
#include "extensionDecls.h"
  • extensionDecls.h
    • can be generated by genStubs.tcl
  • extensionStubInit.c
    • can be generated by genStubs.tcl
  • extensionStubLib.c

You have to create the file extension.h and put all typedef's and macros, that are needed by the API of your extension, in it. At the end of this file just include extensionDecls.h:

genStubs.tcl is included in the "tools" direcotory of the Tcl distribution, and takes a definition file as input.

genStubs.tcl definition file

Unfortunately, I haven't found any description of the file format of such a definition file. But the format seems to be fairly simple and half-way self-explanatory. The *.decls files in the generic directory of the tcl source distribution are examples.

Such a .decls file looks like:

library extensionName
interface extensionName
declare 0 generic {
   int ExtensionName_Function1( Tcl_Interp* interp )
}
declare 1 generic {
        int ExtensionName_Function2( Tcl_Interp* interp, ClientData foobar )
}
... etc ...

That's all. Just wrap normal C prototypes (without the trailing ';') of your API functions, as shown above. Now you could call genStubs.tcl

tclsh path/to/genStubs.tcl <output-Dir> extensionName.decls

GenStubs.tcl only _updates_ extensionDecls.h and extensionStubInit.c; the files already have to exist in the <output-Dir>. So, (under unix) 'touch' the files, or fill your legal stuff as header at top, befor the first genStubs.tcl run.

ExtensionStubInit.c includes (almost) only a global structure, the stubs table of your extension. A 'normal' tcl extension declares itself in the init function of the extension with something like:

Tcl_PkgProvide (interp, "extension",  "1.3")

A tcl extension, that exports a stubs table must, in addition to this, propagate his stubs table. So, instead the above, just use

Tcl_PkgProvideEx (interp, "extension", "1.3", &extensionStubs)

In which &extensionStubs is a pointer to the stubs table of your extension in extensionStubInit.c, so to pacify your compiler/linker you also should add a declaration like:

extern extensionStubs extensionStubs;

to your init.c file.

Now add extensionStubInit.c to the files of your extension and recompile.

The last step is to create the static library, against which other extensions must be linked, if they want to use your extension. This library is complied from the forth of the above mentioned files: extensionStubLib.c. As far, as I'm aware, you have to write your own version of extensionStubLib.c (or to copy one of the existing example, and trim that to your needs). A simple one could look like:

#ifndef USE_TCL_STUBS
#define USE_TCL_STUBS
#endif
#undef USE_TCL_STUB_PROCS

#include "tcl.h"
#include "extension.h"

 /*
 ** Ensure that Tdom_InitStubs is built as an exported symbol.  The other stub
 ** functions should be built as non-exported symbols.
 */

#undef TCL_STORAGE_CLASS
#define TCL_STORAGE_CLASS DLLEXPORT

ExtensionStubs *extensionStubsPtr;

 /*
 **----------------------------------------------------------------------
 **
 **  Extension_InitStubs --
 **
 **        Checks that the correct version of Extension is loaded and that it
 **        supports stubs. It then initialises the stub table pointers.
 **
 **  Results:
 **        The actual version of Extension that satisfies the request, or
 **        NULL to indicate that an error occurred.
 **
 **  Side effects:
 **        Sets the stub table pointers.
 **
 **----------------------------------------------------------------------
 */

char *
Extension_InitStubs (Tcl_Interp *interp, char *version, int exact)
{
  char *actualVersion;
  
  actualVersion = Tcl_PkgRequireEx(interp, "extension", version, exact,
                                                                   (ClientData *) &extensionStubsPtr);
  if (!actualVersion) {
        return NULL;
  }
  
  if (!extensionStubsPtr) {
        Tcl_SetResult(interp,
                                  "This implementation of Extension does not support stubs",
                                  TCL_STATIC);
        return NULL;
  }
  
  return actualVersion;
}   

Compile this and build a static library out of it. Under unix, this means, do something like this (makefile style):

> ar cr libextensionstub$(VERSION).a $(STUBOBJ)   

Well, now you're done, for this side.

How to use a stubs table

Well, at least, this is pretty simple. If you want to use the API of extension1 in extension2, you have to include the extension1.h file (the one from above, for now, extension1 is always the 'extension' from above) into the source files of your extension2. Of course.

The next thing is to add -DUSE_EXTENSION1_STUBS to the compiler flags of your extension2.

And Since you're already in the makefile, just add "-L/path/to/your/extension1/libfile -lextension1stub<version>.a to the libraries, to link extension2 against.

The last step is, to announce that you want to use the API of extension1 in your extension2. As a good tcl citizen you already have an

#ifdef USE_TCL_STUBS
        if (Tcl_InitStubs(interp, "8", 0) == NULL) {
                return TCL_ERROR;
        }
#endif

in the init file of your extension2, for sure. Just add

#ifdef USE_EXTENSION1_STUBS
        if (Extension1_InitStubs(interp, version, exact) == NULL) {
                return TCL_ERROR;
        }
#endif

recompile, and you're done.


Ah, you find this all a bit complicated. Well, I also do. To say the truth, this all wasn't that satisfying. I was able, to make this work on linux, for a given example of extension1 and extension2. But this was not that bit of progress, since under linux, this hole 'using extension1 from extension2' stuff worked (at least under linux and for me) already without this hole stubs hussle, just by exporting the API functions and do the usual. My problem was, that this 'usual' way of doing things - of course, 'usual' depends on the viewpoint - haven't worked at MS plattforms. It still does not.

OK, I take this back, and argue the converse. After some struggling (in fact, a lot of, but, to be fair, most may be due to my limited experiences with the MS build tools) I was able to make this work also under windows.

Now that I have a basic understanding of what to do, to make an extenstion stubs table and how it works I'm still curious about the 'why'. Sure, with an extenstion stubs table, you get some version independence: you can update extension1, and extension2 still works, without recompilation (the same, as your stubs enabled extension still work after you have updated your tcl installation). My experience is, that at least under linux you don't strictly 'must' use an extension stubs table, even if you have an extension2, that uses functions of an extension1 (just export the API functions of extension1, as you would do for every ordinary library, and you're done). But it seems to me, that under windows you 'must' use a extension stubs table, because it doesn't work also the simple way, as with linux. I'm far away to be an expert in dynamic loading stuff (especially under windows). Someone out there, that could confirm or correct my presumption? And are there other OS'es, for which one must use the extension stubs table mechanism, for this extension2 uses extension1 scenario?

Lars H, 18 Dec 2004: Another take on the "why" might be to consider the case of a third extension. What if extension2 and extension3 both need the API of extension1? Would this even be possible with "usual" linking as in the three paragraphs above, if extension2 and extension3 are to be separate?


On a related subject, jcw posted this comment: "Stubs are not limited to tcl.h - this seems to be a common misconception. Did you know that there are over 130 stub definitions for tclInt.h? There's even a (small) third set of stubs for platform-specific calls."