Linking and loading is arguably one of the more difficult things on
Windows.  I'd like to let you know about how some of the things work.  I
think this is important because there are still some issues with that we
need to sort out.  Not sure if I get everything right here, so please
feel free to correct me.

Let's start with loading.  When Windows loads an executable it also
checks the import directory of the image.  There's one entry for each
DLL.  Windows searches for DLLs as described in [1] and loads the image,
if possible at the preferred load address.  If not possible the DLL is
relocated.  This means the location of the DLL is not fixed, and the
code needs to deal with this.  Windows uses an Import Address Table
(IAT) for this.  There's one entry for each imported symbols and all
access is done indirectly through this table.  If the DLL needs to be
relocated only the IAT needs to be updated.

This is not important here, but there are actually two tables.  An
Import Address Table (IAT) and an Import Name Table (INT).  If a symbol
is to be resolved by name it first searches the Import Name Table for
the name and then uses the index for the Import Address Table.

Now to compiling and linking.  Visual C++ uses two declarations, for
exporting symbols from a DLL and importing from one.

__declspec(dllexport) void f();

This tells the compiler to pass a directive to the linker (/LINK option)
that f should be exported.

__declspec(dllimport) void f();

This tells the compiler to resolve symbols through the Import Address
Table.  When f is called the compiler generates an indirect call through
the IAT instead of a simple, direct call.  Similar for data access.

So, when compiling code for a DLL you need to decorate it with
dllexport.  When using it you need to decorate it with dllimport.

When linking a DLL the linker creates the DLL and an import library
(.lib).  All information that's needed for linking against the DLL is in
the import library.

void f();

It's also possible to call functions which are not declared with
dllimport.  That's possible because for each exported function the
linker creates a little magic, a stub function that makes a jump through
the Import Address Table.  This only works for functions, though, and
adds overhead of the indirection.  On the other hand, this way the
compiler does not, and need not, know if the function is just another
function in another compilation unit, a function in a static library or
in a DLL, when compiling the function call.

One convention that is used to get the declaration right goes like this:

#ifdef <project>_EXPORTS
#define <project>_API __declspec(dllexport)
#else
#define <project>_API __declspec(dllimport)
#endif

Whenever a source file is compiled that should be part of the DLL it's
compiled with -D<project>_EXPORTS.  Users of the library just need to
make sure they don't declare <project>_EXPORTS.


So, why is this relevant for Parrot?
Currently it looks like this in F<include/parrot/config.h>

#if defined(PARROT_IN_EXTENSION)
#define PARROT_API __declspec(dllimport)
#define PARROT_DYNEXT_EXPORT __declspec(dllexport)
#else
#define PARROT_API __declspec(dllexport)
#endif

PARROT_IN_EXTENSION is only defined in extensions (dynop, dynpmc).  As
you can see, the default is dllexport, which users and embedders will
use.  This is not a problem for functions, as the stubs will be used, as
described above.  But this doesn't work for data, and for example shows
during F<t/src/compiler.t> tests.

error LNK2001: unresolved external symbol _PMCNULL
t\src\compiler_2.exe : fatal error LNK1120: 1 unresolved externals


Ron

[1] http://msdn2.microsoft.com/en-us/library/ms682586.aspx

Reply via email to