| Autoconf, Automake, and Libtool | ||
|---|---|---|
| <<< Previous | Using GNU Autotools with Cygnus Cygwin | Next >>> |
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:
At link time, a DLL effectively consists of two parts; the DLL itself which contains the shared object code, and an import library which consists of the stub [1] functions which are actually linked into the executable, at a rate of one stub per entry point. Unix has a run time loader which links shared libraries into the main program as it is executed, so the shared library is but a single file.
Pointer comparisons do not always work as expected when the pointers cross a DLL boundary, since you can be comparing the addresses of the stubs in the import library rather than the addresses of the actual objects in the DLL. GCC provides the __declspec extension to alleviate this problem a little.
The search algorithm for the runtime library loader is very different to the algorithms typically used on Unix; I'll explain how to dela with this in the section called Runtime Loading of DLLs.
All of the symbols required by a DLL at runtime, must be resolved at link time. With some creative use of import libraries, it is usually possible to work around this shortcoming, but it is easy to forget this limitation if you are developing on a modern system which has lazy symbol resolution. Be sure to keep it at the back of your mind if you intend to have your package portable to Windows.
Worst of all, is that it is impossible to reference a non-pointer item imported from a DLL. In practice, when you think you have exported a data item from a DLL, you are actually exporting it's address (in fact the address of the address if you take the import library into consideration), and it is necessary to add an extra level of indirection to any non-pointers imported from a DLL to take this into account. The GNU gcc __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.
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");
} |
First of all we will autoconfiscate [2] 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 the section called Creating Libtool Libraries with Automake in the chapter called Using GNU Libtool with configure.in and Makefile.am.
For an explanation of the contents of the rest of this Makefile.am, See the chapter called Introducing GNU Automake.
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 [3] 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 [4] :
$ 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.
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 the section called 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 the section called 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 the section called Creating Libtool Libraries with Automake in the chapter called Using GNU Libtool with configure.in and Makefile.am). 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 |
DLLs built using the recipe described in this chapter can be loaded at runtime in at least three different ways:
Using the Cygwin emulation of the POSIX 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.
Using the Windows LoadLibrary/FreeLibrary/GetProcAddress API.
Using libltdl, which is covered in more detail in the chapter called Using GNU libltdl.
| [1] | In general, a stub function will satisfy the linker's requirements to resolve an undefined symbol at link time, but has no functionality of its own. In this context, the stubs do have some boilerplate code to pass execution flow into the correct full function in the @sc{dll}. |
| [2] | Some people prefer to use the term @dfn{autoconfuse} -- if you should meet any, be sure to tell them about this book |
| [3] | Part of the Binutils port to Windows, and necessary to massage compiler objects into a working @sc{dll}. |
| [4] | This is a shell script for Cygwin which emulates the behaviour of @command{ldd} on @sc{gnu}/Linux, available online from @uref{http://www.oranda.demon.co.uk/dist/ldd}. |
| <<< Previous | Home | Next >>> |
| Writing A Cygwin Friendly Package | Up | Package Installation |