首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

Win32 環境下動態連結庫 (DLL) 程式設計原理完整指南

     編輯:關於VC++

DLL 技術背景與優勢

大型應用程式由多模組組成,各模組完成獨立功能並協作。若將所有原始碼靜態編譯進 EXE,會導致:檔案體積膨脹、記憶體佔用大、編譯時間長、不利單元測試。Windows 提供 DLL (Dynamic Linkable Library) 機制:獨立編譯測試、執行時按需載入、多程式共用、減少資源浪費。Windows 核心功能多以 DLL 實現(如 IE 核心元件可被其他程式呼叫)。

DLL 核心架構

  • 磁碟檔案(通常 .dll 副檔名)
  • 包含全域資料、服務函式、資源
  • 執行時載入進程虛擬位址空間,成為呼叫進程一部分
  • 無衝突時映射至相同位址
  • 匯出函式表提供對外服務
  • Windows 載入時建立函式呼叫與位址對應表

Win32 環境特性

  • 每進程複製讀寫全域變數
  • 跨進程共享記憶體需用記憶體對應檔案或共享資料段
  • DLL 堆疊記憶體從呼叫進程堆疊分配

一、匯出/匯入函式匹配機制

匯出函式宣告

__declspec(dllexport) int MyFunction(int n);

匯入函式宣告

__declspec(dllimport) int MyFunction(int n);

標準做法:標頭檔巨集切換

// MyDLL.h
#ifdef MYDLL_EXPORTS
    #define MYDLL_API __declspec(dllexport)
#else
    #define MYDLL_API __declspec(dllimport)
#endif

MYDLL_API int MyFunction(int n);

模組定義檔 (.def) 替代方案

; MyDLL.def
LIBRARY "MyDLL"
EXPORTS
    MyFunction @1

但 .def 易引發更多問題,建議使用 __declspec。

連結需求

  • 專案需指定匯入庫 (.lib) 給連結器
  • 應用程式至少需有一處呼叫 DLL 函式

二、隱式 vs 顯式連結

特性隱式連結顯式連結
程式碼中指定 DLL 路徑否是 (LoadLibrary 參數)
開發者關心 DLL 載入時機否是
自動產生 .lib 匯入庫是否
載入時機EXE 載入時呼叫 LoadLibrary 時
適用語言C/C++ 原生VB、C#、動態載入需求

隱式連結流程

  1. 建立 DLL 時連結器自動產生 .lib
  2. .lib 包含匯出函式符號名/序數,無實際程式碼
  3. .lib 編譯進應用程式,符號進入 EXE
  4. .lib 含 DLL 檔名(非完整路徑),存入 EXE 內部
  5. 執行時 Windows 依資訊尋找載入 DLL,依符號/序數動態連結

顯式連結範例

// DLL 匯出
extern "C" __declspec(dllexport) double SquareRoot(double d);

// 應用程式顯式連結
typedef double (SQRTPROC)(double);
HINSTANCE hInstance;
SQRTPROC* pFunction;

VERIFY(hInstance = ::LoadLibrary("c:\\winnt\\system32\\mydll.dll"));
VERIFY(pFunction = (SQRTPROC*)::GetProcAddress(hInstance, "SquareRoot"));
double d = (*pFunction)(81.0);  // 呼叫 DLL 函式

顯式連結優勢

  • 執行時決定載入哪個 DLL(如多語言資源 DLL)
  • 可延遲載入、條件載入、錯誤處理更靈活
  • 適用插件架構、選用功能模組

三、符號名 vs 序數連結

  • Win16:符號名效率低,序數為主流
  • Win32:符號名效率改善,微軟建議使用符號名
  • MFC DLL 仍採用序數:EXE 體積較小(無長字串符號名)

四、DllMain 進入點函式

HINSTANCE g_hInstance;

extern "C" int APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
    switch (dwReason)
    {
        case DLL_PROCESS_ATTACH:
            TRACE0("DLL Initializing!\n");
            g_hInstance = hInstance;
            // 初始化:DisableThreadLibraryCalls(hInstance) 可優化
            break;

        case DLL_THREAD_ATTACH:
            // 執行緒建立(若未 DisableThreadLibraryCalls)
            break;

        case DLL_THREAD_DETACH:
            // 執行緒結束
            break;

        case DLL_PROCESS_DETACH:
            TRACE0("DLL Terminating!\n");
            // 清理資源
            break;
    }
    return TRUE;  // 成功
}

未提供 DllMain 時系統引入無操作預設版本。單一執行緒啟動/結束也會呼叫。

五、模組控制代碼 (HINSTANCE/HMODULE)

  • 每個 DLL/EXE 模組有唯一 32 位元 HINSTANCE
  • 代表模組在進程虛擬空間起始位址
  • Win32 中 HINSTANCE == HMODULE 可互換
  • EXE 通常為 0x00400000,DLL 預設 0x10000000
  • 多 DLL 載入位址不同(指定基址或重定位)

取得模組控制代碼

// 取得 EXE 模組控制代碼
HINSTANCE hExe = GetModuleHandle(NULL);

// 取得 DLL 模組控制代碼
HINSTANCE hDll = GetModuleHandle("MyDLL.dll");

資源載入關鍵

FindResource 需 HINSTANCE 參數指定資源來源模組。EXE/DLL 各有獨立資源,需正確指定。

六、DLL 搜尋順序與常見陷阱

搜尋順序

  1. 包含 EXE 的目錄
  2. 進程目前工作目錄
  3. Windows 系統目錄 (System32)
  4. Windows 目錄
  5. PATH 環境變數列出的目錄

經典陷阱

VC++ 專案建立 DLL,複製至系統目錄供應用程式呼叫。修改 DLL 重建後忘記更新系統目錄副本 → 應用程式仍載入舊版本。解決:輸出目錄設為應用程式目錄、或使用延遲載入、或建置後自動複製。

七、DLL 除錯技巧

  • VC++ 從 DLL 專案啟動除錯,指定呼叫 EXE 路徑
  • 設定 PATH 含 DLL 目錄,或複製 DLL 至搜尋順序目錄
  • 使用 OutputDebugString/TRACE 輸出診斷資訊
  • 模組載入/卸載斷點:ntdll!LdrLoadDll、ntdll!LdrUnloadDll
  • 匯出函式斷點:在 DLL 專案中直接對原始碼下斷點

進階主題:DLL 版本管理與相容性

  • 版本資源:VS_VERSION_INFO 嵌入版本資訊
  • Side-by-Side 組件:Manifest 隔離版本衝突
  • 延遲載入:/DELAYLOAD 連結器選項,首次呼叫時載入
  • 安全載入:LoadLibraryEx 配合 LOAD_LIBRARY_SEARCH_* 旗標防止 DLL 劫持
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved