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