Architecture and Implementation of Windows Libraries: Static vs. Dynamic Linking
Windows applications rely heavily on external libraries to execute system-level operations. These libraries typically reside in Dynamic Link Libraries (DLLs). The three foundational components of the Windows subsystem are Kernel32.dll (managing memory, processes, and threads), User32.dll (handling window management and messaging), and Gdi32.dll (managing graphics and text rendering). Other specialized libraries include AdvAPI32.dll for security and registry tasks, ComCtl32.dll for common UI controls, and Ws2_32.dll for networking via Windows Sockets.
When a process starts, the Portable Executable (PE) loader generates a virtual address space. It maps the executable and its required DLLs—identified via the import table—into this space. Only after all modules are mapped does the primary thread begin execution.
Static Link Libraries (LIB)
Static libraries, typically having a .lib extension, are linked during the build process. The linker copies the object code directly into the final executable. This eliminates the need to distribute the library alongside the application but increases the binary size.
Creating a Static Library
In a static library project, you define functions in a header and implement them in a source file. Consider a library named MathCore:
MathCore.h
#pragma once
int Addition(int x, int y);
int Subtraction(int x, int y);
MathCore.cpp
#include "MathCore.h"
int Addition(int x, int y) { return x + y; }
int Subtraction(int x, int y) { return x - y; }
To use this library in a test application, you include the header and inform the linker about the .lib file:
TestApp.cpp
#include <windows.h>
#include <tchar.h>
#include "MathCore.h"
#pragma comment(lib, "MathCore.lib")
int APIENTRY _tWinMain(HINSTANCE hInst, HINSTANCE hPrev, LPTSTR lpCmd, int nShow) {
TCHAR result[128];
wsprintf(result, _T("Sum: %d, Diff: %d"), Addition(10, 5), Subtraction(10, 5));
MessageBox(NULL, result, _T("Math Results"), MB_OK);
return 0;
}
Dynamic Link Libraries (DLL)
Static linking causes code duplication if multiple applications use the same library, wasting both disk and RAM. DLLs solve this by allowing multiple processes to share a single physical copy of the library code in memory. While code is shared via memory mapping, each process maintains its own unique data segment for the DLL.
DLL Project Structure
A DLL can export functions, variibles, or classes. However, exporting C++ classes is often discouraged due to binary compatibility issues across different compiler versions. Exported functions are cataloged in an "Export Table."
Export Macro Pattern: To manage exports and imports efficiently, use a conditional macro in your header:
StringLib.h
#pragma once
#ifdef STRINGLIB_EXPORTS
#define STRING_API extern "C" __declspec(dllexport)
#else
#define STRING_API extern "C" __declspec(dllimport)
#endif
STRING_API int GetStringLength(const char* str);
StringLib.cpp
#include <windows.h>
#define STRINGLIB_EXPORTS
#include "StringLib.h"
int GetStringLength(const char* str) {
return (int)strlen(str);
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID reserved) {
switch (reason) {
case DLL_PROCESS_ATTACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Function Decoration and Calling Conventions
C++ compilers use name mangling to support feature like function overloading. This results in symbols like ?GetStringLength@@YAHPEBD@Z. Using extern "C" prevents this mangling, ensuring compatibility with other languages (like C or Python).
If using the __stdcall convention (common in Win32 APIs), the compiler adds an underscore prefix and an @ suffix followed by the parameter size (e.g., _MyFunc@8). To export a clean name, use a Module Definition (.def) file:
StringLib.def
EXPORTS
GetStringLength
Linking Mechanisms
- Implicit Linking: You provide the
.libimport library (which contains symbol stubs but no code) and the header. The PE loader handles DLL loading at startup. - Explicit Linking: You manually load the DLL using
LoadLibrary()and retrieve function addresses viaGetProcAddress(). This is useful for optional features or loading undocumented APIs.
C Runtime (CRT) Settings
Visual Studio allows configuring how the C runtime is linked:
- /MT /MTd: Links the CRT statically into your binary. Great for portability (no redistributable needed) but increases file size.
- /MD /MDd: Links the CRT dynamically. Requires the Microsoft Visual C++ Redistributable on the target machine.
The DllMain Entry Point
DllMain is the optional entry point for a DLL. It is invoked by the system under four conditions:
- DLL_PROCESS_ATTACH: DLL is being mapped. Good for global initialization.
- DLL_PROCESS_DETACH: DLL is being unmapped. Use for cleanup.
- DLL_THREAD_ATTACH / DETACH: A thread is created or destroyed in the host process.
Note: Avoid complex logic or acquiring locks in DllMain to prevent deadlocks.
Advanced Technique: Delay Loading
Delay loading is a hybrid approach where a DLL is implicitly linked, but not actually loaded until one of its functions is called. This improves application startup time and allows the program to run even if the DLL is missing (provided the specific function is never called).
To implement this, add the DLL to the Linker -> Input -> Delay Loaded DLLs setting in project properties and link against delayimp.lib.
Practical Example: Embedding and Releasing a DLL
You can embed a DLL as a binary resource and extract it to disk at runtime if it's missing, using delay loading to ensure the app starts even before the DLL exists on disk.
void ExtractLibrary(HINSTANCE hInst) {
HRSRC hRes = FindResource(hInst, MAKEINTRESOURCE(IDR_CORE_DLL), _T("BINARY"));
if (hRes) {
HGLOBAL hData = LoadResource(hInst, hRes);
void* pBuffer = LockResource(hData);
DWORD dwSize = SizeofResource(hInst, hRes);
HANDLE hFile = CreateFile(_T("Required.dll"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
DWORD dwWritten;
WriteFile(hFile, pBuffer, dwSize, &dwWritten, NULL);
CloseHandle(hFile);
}
}
}
When combined with __FUnloadDelayLoadedDLL2, you can even unload the library once its task is complete to free resources or allow file deletion.