When your source code statically calls exported DLL functions, or statically accesses exported DLL variables, those references are compiled into your executable's intermediate object files as pointers, whose values get populated at run-time.
When the linker is combining the compiler-generated object files to make the final executable, it has to figure out what all of the compiler-generated references actually refer to. If it can't match a given reference to some piece of code in your executable, it needs to match it to an external DLL instead. So it needs to know which DLLs to even look at, and how those DLLs export things. A DLL may export a given function/variable by name OR by ordinal number, so the linker needs a way to map the identifiers used by your code references to specific entries in the EXPORTS
tables of specific .dll
files (especially in the case where things are exported by ordinals). Static-link .lib
files provide the linker with that mapping information (ie FunctionA
maps to Ordinal 123
in DLL XYZ.dll
, FunctionB
maps to name _FunctionB@4
in DLL ABC.dll
, etc).
The linker can then populate the IMPORTS
table of your executable with information about the appropriate EXPORTS
entries needed, and then make the DLL references in your code point to the correct IMPORTS
entries (if the linker can't resolve a compiler-generate reference to a piece of code in your executable, or to a specific DLL export, it aborts with an "unresolved external" error).
Then, when your executable is loaded at run-time, the OS Loader looks at the IMPORTS
table to know which DLL exports are needed, so it can then load the appropriate DLLs into memory and update the entries in the IMPORTS
table with real memory addresses that are based on each DLL's EXPORTS
table (if a referenced DLL fails to load, or if a referenced export fails to be found, the OS Loader aborts loading your executable). That way, when your code calls DLL functions or accesses DLL variables, those accesses go to the right places.
Things are very different if your source code dynamically accesses DLL functions/variables via explicit calls to GetProcAddress()
at run-time. In that case, static-link .lib
files are not needed for those accesses, since your own code is handling the loading of DLLs into memory and locating the exports that it wants to use.
However, there is a 3rd option that blends the above scenarios together: you can write your code to access the DLL functions/variables statically but use your linker's delay-load feature (if it has one). In that case, you still need static-link .lib
files for each delay-loaded DLL you access, but the linker populates a separate DELAYLOAD
table in your executable with references to the DLL exports, instead of populating the IMPORTS
table. It points the compiler-generated DLL references to stubs in your compiler's RTL that will replace the references with addresses from GetProcAddress()
when the stubs are accessed for the first time at run-time, thus avoiding the need for the references to be populated by the OS Loader at load-time. This allows your executable to run normally even if the DLL exports are not present at load-time, and may not even need to load the DLLs at all if they are never used (of course, if your executable does try to access a DLL export dynamically and it fails to load, your code is likely to crash, but that is a separate issue).