| 類型 | 靜態庫 (.lib) | 動態庫 (.dll + .lib) |
|---|---|---|
| 鏈接時機 | 編譯鏈接階段 | 運行時加載 |
| 代碼副本 | 每個 EXE 獨佔一份 | 內存中共享一份 |
| 發布文件 | 僅 EXE | EXE + DLL |
| 更新維護 | 需重新編譯 EXE | 替換 DLL 即可 (兼容前提下) |
| 生成文件 | .lib (含代碼/符號) | .dll (代碼) + .lib (導入庫,僅符號/地址表) |
.lib 導入庫,在 EXE 導入表 (IAT) 記錄 DLL 名、函數名/序數。加載器啟動進程時自動加載 DLL、解析地址、填充 IAT。xxx.lib#pragma comment(lib, "xxx.lib")#include "xxx.h"Func()LoadLibrary / LoadLibraryEx → GetProcAddress → 函數指針調用 → FreeLibrary
typedef int (__stdcall *AddFunc)(int, int); // 必須匹配調用約定!
HINSTANCE hDll = LoadLibrary(L"Math.dll");
if (!hDll) { Error("LoadLibrary failed"); return; }
AddFunc pAdd = (AddFunc)GetProcAddress(hDll, "Add");
if (!pAdd) { Error("GetProcAddress failed"); FreeLibrary(hDll); return; }
int result = pAdd(3, 5); // 調用
printf("Result: %d\n", result);
FreeLibrary(hDll);
錯誤現象:Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call
根因:DLL 導出函數用 __stdcall (WinAPI 默認),調用端函數指針類型定義為 __cdecl (C 默認),導致棧平衡錯誤。
| 特性 | __cdecl (C 默認) | __stdcall (WinAPI/Pascal) |
|---|---|---|
| 參數壓棧順序 | 右到左 | 右到左 |
| 棧清理責任 | 調用者 | 被調用者 (RET n) |
| 函數名修飾 | _FuncName | _FuncName@n (n=參數字節數) |
| 可變參數 | 支持 | 不支持 |
extern "C" __declspec(dllexport) int __stdcall Add(int a, int b);typedef int (__stdcall *AddFunc)(int, int); 或 typedef int (WINAPI *AddFunc)(int, int); (#define WINAPI __stdcall)
// MyDll.h
#ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif
extern "C" MYDLL_API int __stdcall Add(int a, int b);
extern "C" MYDLL_API void __stdcall Hello();
// MyDll.cpp
#define MYDLL_EXPORTS
#include "MyDll.h"
int __stdcall Add(int a, int b) { return a+b; }
__pfnDliNotifyHook2 實現加載失敗回調/備選路徑。dumpbin /exports Math.dll 確認名稱修飾是否符合預期。隱式鏈接適合核心依賴,顯式鏈接適合插件/可選功能/插件熱更新。**調用約定一致性是跨模塊調用的生命線**。現代 C++ 開發建議:暴露 C 接口 (extern "C") + 顯式鏈接工廠函數返回 C++ 介面智能指針,內部實現完全隱藏,實現二進制兼容與封裝。