[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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:
__declspec
extension to alleviate this problem a little.
__declspec
extension can handle this automatically
too, at the expense of obfuscating your code a little.
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] | [ ? ] |
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] | [ ? ] |
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] | [ ? ] |
‘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] | [ ? ] |
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:
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.
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.
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.
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
.
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 Creating Libtool Libraries with Automake). One possible solution is:
#if defined WIN32 && defined DLL_EXPORT char libhello_is_dll (void) { return 1; } #endif /* WIN32 && DLL_EXPORT */ |
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] | [ ? ] |
DLLs built using the recipe described in this chapter can be loaded at runtime in at least three different ways:
dlopen
/dlclose
/dlsym
API. Note however that
the emulation is broken up until at least version b20.1, and
dlopen(NULL)
doesn’t work at all.
LoadLibrary
/FreeLibrary
/GetProcAddress
API.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] |
This document was generated by Ben Elliston on July 10, 2015 using texi2html 1.82.