Extension Stubs Tables

Extension Stubs Tables is about building an extension that provides a stubs table of C functions that other extensions can use.

Description

The Makefile.in in SampleExtension includes a recipe to compile a stubs libary, and the TEA_MAKE_LIB in tclconfig's tcl.m4 configures the Makefile to build it if appropriate. Add the needed source files to TCL_ADD_STUB_SOURCES in configure.ac It is also possible to create an extension that provides stubs using critcl. See the api command.

Extensions that Provide Stubs

In addition to Tcl, which exports a stubs table, extensions that provide stubs tables include:

nsf
Provides stubs tables for Tcl 8.5, 8.6, and 8.7.
Trf
Snack
Also includes a simple example of using the snack API from another Tcl extension
Tk
TclOO
TclXml, TclDOM, TclXSLT
TclDOM/libxml2 and TDOM both import stubs tables and export stubs tables of their own.

How to Provide a Stubs Table

Add the following files:

  • extension.h
    • includes all typedef'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

genStubs.tcl is included in the "tools" directory of the Tcl project.

genStubs.tcl Definition File

genStubs.tcl requires a definition file as input. The format of the definition file is fairly self-explanatory:

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

Each definition looks mostly like a standard C prototype, but without the trailing semilcolon (;).

Examples are tcl.decls , tclInt.decls , tclOO.decls , and tclTomMath.decls .

To run genStubs.tcl:

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

genStubs.tcl updates `extensionDecls.h and extensionStubInit.c, which must already exist in the output directory. Everything in those files after the first line that matches *!BEGIN!*`, or the end of the file, whichever comes first, is replaced by generated content.

ExtensionStubInit.c is mainly a global structure: The stubs table of your extension. A Tcl extension typically declares itself in the initialization function of the extension with something like:

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

A tcl extension, that exports a stubs table must also propagate its stubs table, so instead the above snippet is modified to:

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

where &extensionStubs is a pointer to the stubs table in the in extensionStubInit.c. To pacify the compiler add a declaration like:

extern extensionStubs extensionStubs;

to the compilation unit containing the initialization function for the extension.

Add extensionStubInit.c to the files of your extension and recompile.

Finally, compile and link extensionStubLib.c into a static archive against which other extensions may link to obtain the stubs table and use the extension. The contents of extensionStubLib.c may be cribbed from tkStubLib.c or tclOOStubLib.c . It should look something 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)   

Example: TclOO

To provide a stubs table, TclOO does the following:

  1. Provides tclOO.h , which defines Tcl__OOInitStubs when USE_TCLOO_STUBS is true.
  2. Uses genStubs.tcl to generates tclOODecls.h and tclOOStubInit.c from tclOO.decls .
  3. Implements TclOOInitializeStubs in tclOOStubLib.c .
  4. In the he self-standing version of TclOO tclOOStubLib.c also includes its own implementation of isDigit and RequireExactVersion , copied from Tk/generic/tkStubLib.c , to work around variance in the C library implementation of isDigit on Windows. At some point this issue should be fixed in Tcl, after which these local implementations can be removed.

How to Use a Stubs Table

To use the API of extension1 in extension2, include the extension1.h file. Next, add -DUSE_EXTENSION1_STUBS (or whatever macro extension1 prescribes) to the compiler flags for extension2.

In the makefile, add something like

-L/path/to/your/extension1/libfile -lextension1stub<version>

Finally, initialize the stubs table for extension1. As a good tcl citizen you've already included the following in the initialization function for extension2:

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

Now add

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

That's all.

Discussion

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 2004-12-18: 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."

Page Authors

kbk
de
PYK