Back: A configure.in for DLLs
Forward: Runtime Loading of DLLs
 
FastBack: Runtime Loading of DLLs
Up: DLLs with Libtool
FastForward: Package Installation
Top: Autoconf, Automake, and Libtool
Contents: Table of Contents
Index: Index
About: About this document

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");
}

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:

  1. 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 */
    

  2. Link a program with the library, and check whether it is a DLL by seeing if the link succeeded.

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