Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Architecture and Implementation of Windows Libraries: Static vs. Dynamic Linking

Tech May 13 1

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

  1. Implicit Linking: You provide the .lib import library (which contains symbol stubs but no code) and the header. The PE loader handles DLL loading at startup.
  2. Explicit Linking: You manually load the DLL using LoadLibrary() and retrieve function addresses via GetProcAddress(). 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.

Tags: Windows API

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.