Version 3 of DLLEXPORT and DLLIMPORT

Updated 2014-01-12 21:51:40 by dkf

Unlike most Unices, on Windows you need to explicitly specify which functions are publically visible when creating a shared library.

One way to do this is to write a .DEF file listing the symbols to be exported, and pass that to the linker when building the .DLL. (See the linker documentation for details if you're interested; but the next way is easier).

The easier way is to use an MSVC language extension to declare publically visible functions in the source code:

    extern int __declspec(dllexport) Foo_Init(Tcl_Interp *);
    ...
    int __declspec(dllexport) Foo_Init(Tcl_Interp *interp)
    {
        ...
    }

Most other compilers for Windows (including the MinGW and CYGWIN versions of gcc) have adopted this extension.

Naturally, the __declspec(dllexport) bit should be hidden inside a preprocessor macro for cross-platform portability. Tcl.h defines such a macro: the symbol DLLEXPORT expands to the appropriate Windows voodoo if the compilation environment supports it, and to the empty string otherwise.

    int DLLEXPORT Foo_Init(Tcl_Interp *interp) { ... }

That should be sufficient for Tcl extensions' _Init() and _SafeInit() routines. In the general case, though, more preprocessor trickery is needed.

Windows also requires that functions appearing in an external shared library be declared with the "__declspec(dllimport)" storage class. Instead of creating two copies of each header file — one with __declspec(dllimport) for users of the library and one with __declspec(dllexport) for the library itself — the usual idiom is to say:

        #ifdef BUILD_FOO
        #    define FOOAPI __declspec(dllexport)
        #else
        #    define FOOAPI __declspec(dllimport)
        #endif
        ...
        extern int FOOAPI foo_fiddle();
        extern int FOOAPI foo_faddle();
        extern int FOOAPI foo_twaddle();

in the relevant header files, then compile the library with -DBUILD_FOO. Tcl does this, too; the macro TCL_STORAGE_CLASS expands to __declspec(dllexport) when building Tcl on Windows, __declspec(dllimport) when linking directly against the Tcl shared library, and to the empty string when linking with the Stubs library and on non-Windows platforms.

Many Tcl extensions use preprocessor contortions like:

  #undef TCL_STORAGE_CLASS
  #ifdef BUILD_sampleextension
  #  define TCL_STORAGE_CLASS DLLEXPORT
  #endif
  ...
  EXTERN int Sample_Init();
  …

This is overly complex and error-prone; I recommend not doing this. Basically, this is hijacking Tcl's preprocessor voodoo for reuse by other packages. It's simpler, clearer, and safer for extensions to just define their own voodoo.