Autoconf, Automake, and Libtool: Handling Data Exports from DLLs

| ### 25.4.4 Handling Data Exports from DLLs Unfortunately, things are not quite that simple in reality, except in the rare cases where no data symbols are exported across a DLL boundary. If you look back at the example in A configure.in for DLLs, you will notice that the Libtool object, ``hello.lo'` was built with the preprocessor macro ``DLL_EXPORT'` defined. Libtool does this deliberately so that it is possible to distinguish between a static object build and a Libtool object build, from within the source code. Lets add a data export to the DLL source to illustrate: The ``hello.h'` header must be changed quite significantly:
    #ifndef HELLO_H
    #define HELLO_H 1

    #if HAVE_CONFIG_H
    #  include <config.h>
    #endif

    #ifdef _WIN32
    #  ifdef DLL_EXPORT
    #    define HELLO_SCOPE         __declspec(dllexport)
    #  else
    #    ifdef LIBHELLO_DLL_IMPORT
    #      define HELLO_SCOPE       extern __declspec(dllimport)
    #    endif
    #  endif
    #endif
    #ifndef HELLO_SCOPE
    #  define HELLO_SCOPE           extern
    #endif

    HELLO_SCOPE const char *greet;
    extern int hello (const char *who);

    #endif /* !HELLO_H */
The nasty block of preprocessor would need to be shared among all the source files which comprise the ``libhello.la'` Libtool library, which in this example is just ``hello.c'`. It needs to take care of five different cases: compiling ``hello.lo'` When compiling the Libtool object which will be included in the DLL, we need to tell the compiler which symbols are exported data so that it can do the automatic extra dereference required to refer to that data from a program which uses this DLL. We need to flag the data with `__declspec(dllexport)`, See section 25.4 DLLs with Libtool. compilation unit which will link with ``libhello-0-0-0.dll'` When compiling an object which will import data from the DLL, again we need to tell the compiler so that it can perform the extra dereference, except this time we use `extern __declspec(dllimport)`. From the preprocessor block, you will see that we need to define ``LIBHELLO_DLL_IMPORT'` to get this define, which I will describe shortly. compiling ``hello.o'` When compiling the object for inclusion in the static archive, we must be careful to hide the `__declspec()` declarations from the compiler, or else it will start dereferencing variables for us by mistake at runtime, and in all likelihood cause a segmentation fault. In this case we want the compiler to see a simple `extern` declaration. compilation unit which will link with ``libhello.a'` Similarly, an object which references a data symbol which will be statically linked into the final binary from a static archive must not see any of the `__declspec()` code, and requires a simple `extern`. non Windows host It seems obvious, but we must also be careful not to contaminate the code when it is compiled on a machine which doesn't need to jump through the DLL hoops. The changes to ``hello.c'` are no different to what would be required on a Unix machine. I have declared the `greet` variable to allow the caller to override the default greeting:
    #if HAVE_CONFIG_H
    #  include <config.h>
    #endif

    #include <stdio.h>

    #include "hello.h"

    const char *greet = "Hello";

    int
    hello (const char *who)
    {
        printf("%s, %s!\n", greet, who);
        return 0;
    }
Again, since the DLL specific changes have been encapsulated in the ``hello.h'` file, enhancements to ``main.c'` are unsurprising too:
    #if HAVE_CONFIG_H
    #  include <config.h>
    #endif

    #include "hello.h"

    int
    main (int argc, const char *const argv[])
    {
        if (argc > 1)
          {
            greet = argv[1];
          }
        return hello("World");
    }
1. The final thing to be aware of is to be careful about ensuring that ``LIBHELLO_DLL_IMPORT'` is defined when we link an executable against the ``libhello'` DLL, but not defined if we link it against the static archive. It is impossible to automate this completely, particularly when the executable in question is from another package and is using the installed ``hello.h'` header. In that case it is the responsibility of the author of that package to probe the system with `configure` to decide whether it will be linking with the DLL or the static archive, and defining ``LIBHELLO_DLL_IMPORT'` as appropriate. Things are a little simpler when everything is under the control of a single package, but even then it isn't quite possible to tell for sure whether Libtool is going to build a DLL or only a static library. For example, if some dependencies are dropped for being static, Libtool may disregard ``-no-undefined'` (see section 11.2.1 Creating Libtool Libraries with Automake). One possible solution is: Define a function in the library that invokes ``return 1'` from a DLL. Fortunately that's easy to accomplish thanks to ``-DDLL_EXPORT'`, in this case, by adding the following to ``hello.c'`:
       #if defined WIN32 && defined DLL_EXPORT
       char
       libhello_is_dll (void)
       {
         return 1;
       }
       #endif /* WIN32 && DLL_EXPORT */
Link a program with the library, and check whether it is a DLL by seeing if the link succeeded. To get cross builds to work, you must, in the same vein, test whether linking a program which calls ``libhello_is_dll'` succeeds to tell whether or not to define ``LIBHELLO_DLL_IMPORT'`. As an example of building the ``hello'` binary we can add the following code to ``configure.in'`, just before the call to ``AC_OUTPUT'`:
    # ----------------------------------------------------------------------
    # Win32 objects need to tell the header whether they will be linking
    # with a dll or static archive in order that everything is imported
    # to the object in the same way that it was exported from the
    # archive (extern for static, __declspec(dllimport) for dlls)
    # ----------------------------------------------------------------------
    LIBHELLO_DLL_IMPORT=
    case "$host" in
    *-*-cygwin* | *-*-mingw* )
      if test X"$enable_shared" = Xyes; then
        AC_TRY_LINK_FUNC([libhello_is_dll],
                         [LIBHELLO_DLL_IMPORT=-DLIBHELLO_DLL_IMPORT])
      fi
      ;;
    esac
    AC_SUBST(LIBHELLO_DLL_IMPORT)
And we must also arrange for the flag to be passed while compiling any objects which will end up in a binary which links with the dll. For this simple example, only ``main.c'` is affected, and we can add the following rule to the end of ``Makefile.am'`:
    main.o: main.c
            $(COMPILE) @LIBHELLO_DLL_IMPORT@ -c main.c
In a more realistic project, there would probably be dozens of files involved, in which case it would probably be easier to move them all to a separate subdirectory, and give them a ``Makefile.am'` of their own which could include:
    CPPFLAGS        = @LIBHELLO_DLL_IMPORT@
Now, lets put all this into practice, and check that it works:
    $ make
    cd . && aclocal
    cd . && automake --foreign Makefile
    cd . && autoconf
    ...
    checking for gcc option to produce PIC ... -DDLL_EXPORT
    checking if gcc PIC flag  -DDLL_EXPORT works... yes
    ...
    checking whether to build shared libraries... yes
    ...
    gcc -DHAVE_CONFIG_H -I. -I. -I. -g -O2 -Wp,-MD,.deps/hello.pp \
    -c  -DDLL_EXPORT -DPIC hello.c -o .libs/hello.lo
    gcc -DHAVE_CONFIG_H -I. -I. -I. -g -O2 -Wp,-MD,.deps/hello.pp \
    -c hello.c -o hello.o >/dev/null 2>&1
    ...
    gcc -DHAVE_CONFIG_H -I. -I. -I.     -g -O2 -DLIBHELLO_DLL_IMPORT \
    -c main.c
    ...
    gcc -g -O2 -o ./libs/hello main.o .libs/libimp-hello-0-0-0.a \
    -Wl,--rpath -Wl,/usr/local/lib
    creating hello
    ...
    $ ./hello
    Hello, World!
    $ ./hello Howdy
    Howdy, World!
The recipe also works if I use only the static archives:
    $ make clean
    ...
    $ ./configure --disable-shared
    ...
    checking whether to build shared libraries... no
    ...
    $ make
    ...
    gcc -DHAVE_CONFIG_H -I. -I. -I. -f -O2 -Wp,-MD,.deps/hello.pp \
    -c hello.c -o hello.o
    ...
    ar cru ./libs/libhello.a  hello.o
    ...
    gcc -DHAVE_CONFIG_H -I. -I. -I.     -g -O2 -c main.c
    ...
    gcc -g -O2 -o hello main.o ./.libs/libhello.a
    $ ./hello
    Hello, World!
    $ ./hello "G'Day"
    G'day, World!
And just to be certain that I am really testing a new statically linked executable:
    $ ldd ./hello
    hello.exe       -> /tmp/hello.exe
    cygwin1.dll     -> /usr/bin/cygwin1.dll
    kernel32.dll    -> /WINNT/system32/kernel32.dll
    ntdll.dll       -> /WINNT/system32/ntdll.dll
    advapi32.dll    -> /WINNT/system32/advapi32.dll
    user32.dll      -> /WINNT/system32/user32.dll
    gdi32.dll       -> /WINNT/system32/gdi32.dll
    rpcrt4.dll      -> /WINNT/system32/rpcrt4.dll

This document was generated by Gary V. Vaughan on February, 8 2006 using texi2html