Updated 2015-12-21 18:11:48 by pooryorick

Extension Stubs Tables is about building an extension that exports a Stubs table for use at the C level by other extensions.

Description  edit

tclconfig provides tcl.m4, which can build a stubs library, and the Tcl SampleExtension provides a template configure.ac that can be filled in with the needed information to build the stubs library. If you write an extension using critcl, it provides the api command to build and export a stubs table for the extension.

In addition to Tcl, which exports a stubs table, extensions that export stubs tables include:
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 Export a Stubs Table  edit

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 extensionDecls.h:

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

genStubs.tcl Definition File  edit

There may not be a formal description of the file format of such a definition file, but it seems to be fairly simple and half-way self-explanatory. The tcl.decls, tclInt.decls, tclOO.decls, tclTomMath.decls files serve as examples. Each .decls file looks something 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, which must already exist in the output directory, so (under unix) touch the files, or fill your legal stuff as header at top, before executing genStubs.tcl for the first time.

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)   

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

Example: TclOO  edit

The following steps describes how TclOO does it:

  1. Provide a tclOO.h that defines Tcl__OOInitStubs depending on USE_TCLOO_STUBS.
   2 . Use [tclOO.decls] to generate [tclOODecls.h], [tclOOStubInit.c].

  1. Implement TclOOInitializeStubs in tclOOStubLib.c.
  2. Additionally, when bundled as a separate package, pull in some support functions from Tk/generic/tkStubLib.c.

It's a little rough to get it all done, so if anyone is looking for an opportunity to step in an smooth out the Tcl ecosystem, here is one.

How to Use a Stubs Table  edit

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  edit

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  edit

kbk
de
PYK