[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

25.4 DLLs with Libtool

Windows’ DLLs, are very different to their nearest equivalent on Unix: shared libraries. This makes Libtool’s job of hiding both behind the same abstraction extremely difficult – it is not fully implemented at the time of writing. As a package author that wants to use DLLs on Windows with Libtool, you must construct your packages very carefully to enable them to build and link with DLLs in the same way that they build and link with shared libraries on Unix.

Some of the difficulties that must be addressed follow:

Cygwin support in Libtool is very new, and is being developed very quickly, so newer versions generally improve vastly over their predecessors when it comes to Cygwin, so you should get the newest release you can. The rest of this section is correct with respect to Libtool version 1.3.5.

In some future version, Libtool might be able to work as transparently as Autoconf and Automake, but for now designing your packages as described in this chapter will help Libtool to help us have DLLs and Unix shared libraries from the same codebase.

The bottom line here is that setting a package up to build and use modules and libraries as both DLLs and Unix shared libraries is not straightforward, but the rest of this section provides a recipe which I have used successfully in several projects, including the module loader for GNU m4 1.5 which works correctly with DLLs on Windows. Lets create hello world as a DLL, and an executable where the runtime loader loads the DLL.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

25.4.1 DLL Support with GNU Autotools

Here are the contents of the three source files used as an example for the remainder of this chapter (for brevity, they are missing most of the special code one would normally use to maximise portability):

hello.h’ documents the interface to ‘libhello.dll’:

 
#ifndef HELLO_H
#define HELLO_H 1

extern int hello (const char *who);

#endif /* !HELLO_H */

hello.c’ is the implementation of ‘libhello.dll’:

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

#include <stdio.h>

#include "hello.h"

int
hello (const char *who)
{
    printf("Hello, %s!\n", who);
    return 0;
}

main.c’ is the source for the executable which uses ‘libhello.dll’:

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

#include "hello.h"

int
main (int argc, const char *const argv[])
{
    return hello("World");
}

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

25.4.2 A Makefile.am for DLLs

First of all we will autoconfiscate(66) the source files above with a minimal setup:

Makefile.am’ is used to generate the ‘Makefile.in’ template for the ‘configure’ script:

 
## Process this file with automake to produce Makefile.in.

lib_LTLIBRARIES		= libhello.la
libhello_la_SOURCES     = hello.c
libhello_la_LDFLAGS     = -no-undefined -version-info 0:0:0

include_HEADERS         = hello.h

bin_PROGRAMS            = hello
hello_SOURCES           = main.c
hello_LDADD             = libhello.la

The new feature introduced in this file is the use of the ‘-no-undefined’ flag in the libhello_la_LDFLAGS value. This flag is required for Windows DLL builds. It asserts to the linker that there are no undefined symbols in the ‘libhello.la’ target, which is one of the requirements for building a DLL outlined earlier. See section Creating Libtool Libraries with Automake.

For an explanation of the contents of the rest of this ‘Makefile.am’, See section Introducing GNU automake.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

25.4.3 A configure.in for DLLs

configure.in’ is used to generate the ‘configure’ script:

 
# Process this file with autoconf to create configure.

AC_INIT(hello.h)
AM_CONFIG_HEADER(config.h:config.hin)
AM_INIT_AUTOMAKE(hello, 1.0)

AC_PROG_CC
AM_PROG_CC_STDC
AC_C_CONST
AM_PROG_LIBTOOL

AC_OUTPUT(Makefile)

The ‘AC_PROG_CC’ and ‘AM_PROG_CC_STDC’ macros in the ‘configure.in’ above will conspire to find a suitable compiler for the C code in this example, and to discover any extra switches required to put that compiler into an ANSI mode. I have used the const keyword in the sources, so I need to specify the ‘AC_C_CONST’ macro, in case the compiler doesn’t understand it, and finally I have specified the ‘AM_PROG_LIBTOOL’ macro since I want the library to be built with Libtool.

In order to set the build environment up we need to create the autogenerated files:

 
$ ls
Makefile.in    hello.c   main.c
configure.in   hello.h
$ aclocal
$ autoheader
$ libtoolize --force --copy
$ automake --foreign --add-missing --copy
automake: configure.in: installing ./install-sh
automake: configure.in: installing ./mkinstalldirs
automake: configure.in: installing ./missing
$ autoconf
$ ls
Makefile.am    config.hin     hello.c       ltmain.sh      stamp-h.in
Makefile.in    config.sub     hello.h       main.c
aclocal.m4     configure      install-sh    missing
config.guess   configure.in   ltconfig      mkinstalldirs

If you have already tried to build DLLs with Libtool, you have probably noticed that the first point of failure is during the configuration process. For example, running the new configure script you might see:

 
...
checking if libtool supports shared libraries... yes
checking if package supports dlls... no
checking whether to build shared libraries... no
...

libtool provides a macro, ‘AC_LIBTOOL_WIN32_DLL’, which must be added to a package’s ‘configure.in’ to communicate to the libtool machinery that the package supports DLLs. Without this macro, libtool will never try to build a DLL on Windows. Add this macro to ‘configure.in’ before the ‘AM_PROG_LIBTOOL’ macro, and try again:

 
$ make
cd . && aclocal
cd . && automake --foreign Makefile
cd . && autoconf
...
checking if libtool supports shared libraries... yes
checking if package supports dlls... 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
mv -f .libs/hello.lo hello.lo
...
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!

If you run this and watch the full output of the ‘make’ command, Libtool uses a rather contorted method of building DLLs, with several invocations each of dlltool and gcc. I have omitted these from the example above, since they really are very ugly, and in any case are almost incomprehensible to most people. To see it all in its full horror you can always examine the output after running the commands yourself! In a future release of Cygwin, recent work on the binutils linker by DJ Delorie, will allow gcc to link DLLs in a single pass using the same syntax used on other systems to produce shared libraries. Libtool will adopt this method when it becomes available, deprecating the use of dlltool.

I have extracted the interesting lines from amongst the many calls to dlltool(67) and gcc generated by make in the shell log. The main thing to notice is that we have a ‘hello’ binary, which is executable, and which gives the right result when we run it! From the partial log above, it certainly appears that it has built ‘libhello’ as a DLL and linked that into ‘hello’, but just to double check we can use ldd(68):

 
$ libtool --mode=execute ldd ./hello
lt-hello.exe    -> /tmp/.libs/lt-hello.exe
libhello-0-0-0.dll      -> /tmp/.libs/libhello-0-0-0.dll
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

So now you know how to build and link a simple Windows DLL using GNU Autotools: You add ‘-no-undefined’ to the Libtool library ‘LDFLAGS’, and include the ‘AC_LIBTOOL_WIN32_DLL’ macro in your ‘configure.in’.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

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 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 ‘libhelloDLL, 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 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

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

25.4.5 Runtime Loading of DLLs

DLLs built using the recipe described in this chapter can be loaded at runtime in at least three different ways:


[ < ] [ > ]   [ << ] [ Up ] [ >> ]

This document was generated by Ben Elliston on July 10, 2015 using texi2html 1.82.