|
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:
-
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
|
|