本文系統性整理 VC++ DLL 開發核心知識:導出函數聲明兩大方式、靜態/動態呼叫對比、DllMain 入口函數詳解、呼叫慣例 `__stdcall` 與 `__cdecl` 差異、導出變數與類別實作。
// Lib.h extern "C" __declspec(dllexport) int add(int x, int y);
; Lib.def
LIBRARY DllTest
EXPORTS
add @1 ; @1 指定序數,GetProcAddress 可用 MAKEINTRESOURCE(1) 取得
.def 規則:`LIBRARY` 指定 DLL 名稱;`EXPORTS` 列出函數名,可加 `@n` 指定序數;註解以 `;` 開頭獨佔一行。
| 特性 | 動態載入 (LoadLibrary/GetProcAddress) | 靜態連結 (隱式連結) |
|---|---|---|
| 載入時機 | 執行期程式碼控制 | 進程啟動時由載入器自動載入 |
| 靈活度 | 高(可延遲載入、條件載入、熱插拔) | 低(啟動必須存在 DLL) |
| 函數取得 | `GetProcAddress(hDll, "func")` 或序數 | 直接呼叫函數名,如同本地函數 |
| 釋放 | `FreeLibrary(hDll)` 手動釋放 | 進程結束自動釋放(參考計數歸零) |
| 所需檔案 | 僅 .dll | 需 .lib (導入庫) + .h + .dll |
typedef int(*LPADD)(int, int);
HINSTANCE hDll = LoadLibrary("DllTest.dll");
if (hDll) {
LPADD pAdd = (LPADD)GetProcAddress(hDll, "add");
if (pAdd) printf("%d", pAdd(2,3));
FreeLibrary(hDll);
}
#pragma comment(lib, "DllTest.lib")
extern "C" __declspec(dllimport) int add(int x, int y);
int main() { printf("%d", add(2,3)); }
`.lib` 為導入庫,僅含導出符號重定位資訊與 DLL 檔名,連結時嵌入 EXE,載入器據此尋找並載入 DLL。
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason, LPVOID lpReserved) {
switch (ul_reason) {
case DLL_PROCESS_ATTACH: // 進程載入 DLL
// 初始化、分配資源、DisableThreadLibraryCalls(hModule) 優化
break;
case DLL_THREAD_ATTACH: // 執行緒建立
break;
case DLL_THREAD_DETACH: // 執行緒結束
break;
case DLL_PROCESS_DETACH: // 進程卸載 DLL
// 清理資源
break;
}
return TRUE; // FALSE 會導致載入失敗(僅 PROCESS_ATTACH 有效)
}
| 項目 | __stdcall (WINAPI, CALLBACK, APIENTRY) | __cdecl (C/C++ 預設) |
|---|---|---|
| 堆疊清理 | 被呼叫者清理 | 呼叫者清理 |
| 參數壓入順序 | 右至左 | 右至左 |
| 修飾名稱 (C 連結) | `_func@n` (n=參數位元組數) | `_func` |
| 可變參數 | 不支援 | 支援 (如 printf) |
| 跨語言呼叫 | Windows API 標準,VB/Delphi 相容 | C/C++ 內部預設 |