注意:原文章標題為「Java 工具類:字串操作與 MD5」,但內容實為 C/C++ DLL 靜態連結庫教學。本文依實際內容重寫。
動態連結庫 (DLL, Dynamic Link Library) 是 Windows 平台實現代碼複用、模組化、進程間共享記憶體的核心機制。本文詳述兩種建立 DLL 的方式(__declspec(dllexport) 與 .def 檔案),以及隱式/顯式兩種呼叫模式的完整流程。
.dll,多進程可共享單一記憶體副本。__declspec(dllexport/dllimport) 建立 DLLVisual Studio → 新專案 → Win32 Dynamic-Link Library → 專案名 DllDemo。
DllDemo.h - 統一導出/導入宏#ifdef DllDemo_EXPORTS
#define DllAPI __declspec(dllexport) // 建置 DLL 時導出
#else
#define DllAPI __declspec(dllimport) // 使用 DLL 時導入
#endif
extern "C" { // C 語言連結規範,防止 C++ Name Mangling
DllAPI int __stdcall Max(int a, int b); // __stdcall: WinAPI 標準呼叫慣例
}
DllDemo.cpp#include "DllDemo.h"
DllAPI int __stdcall Max(int a, int b) {
if (a == b) return 0; // 原碼返回 NULL 不當,改為 0 或特定錯誤碼
return (a > b) ? a : b;
}
編譯生成 DllDemo.dll、DllDemo.lib、DllDemo.h 三個核心產物。
.def 建立 DLL不修改原始碼,透過 .def 檔案聲明導出符號。
DllDemo.h,移除 #include "DllDemo.h"DllDemo.defLIBRARY MyDll
EXPORTS
Max @1
LIBRARY:DLL 內部名稱 (可選)。EXPORTS:導出符號列表。Max @1:導出函數 Max,序數為 1 (可選,但建議指定)。// DllDemo.cpp
extern "C" int __stdcall Max(int a, int b) {
if (a == b) return 0;
return (a > b) ? a : b;
}
同樣生成 .dll、.lib,但無標頭檔,呼叫端需自行聲明。
連結時期解析符號,執行期自動載入 DLL。
DllDemo.dll、DllDemo.lib、DllDemo.h 到呼叫端專案目錄。Caller.h:#define DllAPI __declspec(dllimport)
#pragma comment(lib, "DllDemo.lib") // 告訴連結器連結此 .lib
extern "C" {
DllAPI int __stdcall Max(int a, int b);
}
Caller.cpp:#include "Caller.h" #includeint main() { int val = Max(2, 9); printf("Max = %d\n", val); // 輸出: Max = 9 return 0; }
優點:程式碼簡潔,像呼叫本地函數一樣。缺點:啟動時若找不到 DLL 會直接報錯無法啟動。
執行期手動 LoadLibrary、GetProcAddress、FreeLibrary,無需 .lib 檔案。
DllDemo.dll 到呼叫端目錄或系統路徑。dumpbin /exports DllDemo.dll 確認導出符號裝飾名稱 (關鍵步驟)。// 輸出範例:
1 0 00001000 _Max@8 = @ILT+10(_Max@8) // __stdcall 裝飾: _FuncName@參數總位元組數
#include#include // 函數指標類型定義 (必須符合 __stdcall 慣例) typedef int (__stdcall *LPMAX)(int, int); int main() { // 1. 載入 DLL HINSTANCE hDll = LoadLibrary("DllDemo.dll"); if (!hDll) { std::cerr << "LoadLibrary 失敗, 錯誤碼: " << GetLastError() << std::endl; return 1; } // 2. 獲取函數地址 (兩種方式) // 方式 A: 使用裝飾名稱 (從 dumpbin 獲得) // LPMAX pMax = (LPMAX)GetProcAddress(hDll, "_Max@8"); // 方式 B: 使用序數 (若 .def 指定 @1) - 更穩健 LPMAX pMax = (LPMAX)GetProcAddress(hDll, (LPCSTR)MAKEINTRESOURCE(1)); if (!pMax) { std::cerr << "GetProcAddress 失敗, 錯誤碼: " << GetLastError() << std::endl; FreeLibrary(hDll); return 1; } // 3. 呼叫函數 int val = pMax(2, 9); std::cout << "Max = " << val << std::endl; // 輸出: Max = 9 // 4. 釋放 DLL FreeLibrary(hDll); return 0; }
適用場景:插件系統、延遲載入大型 DLL、多版本 DLL 動態選擇、無標頭檔/導入庫時。
| 項目 | 說明 |
|---|---|
extern "C" | 禁用 C++ Name Mangling,確保導出符號為純函數名 (如 Max) 而非裝飾名 (如 ?Max@@YGHHH@Z),實現跨語言呼叫 (C、C#、VB、Python ctypes 等)。 |
__stdcall | Win32 API 標準呼叫慣例:參數由右至左壓棧、被呼叫者清理棧、函數名裝飾為 _Name@ByteCount。函數指標類型必須宣告為 __stdcall,否則棧平衡錯誤導致崩潰。 |
__declspec(dllexport) | 告訴編譯器將符號放入 DLL 導出表。配合 dllimport 宏切換,同一標頭檔服務建置與使用兩端。 |
.def 檔案 | 模組定義檔,非侵入式導出聲明。支援序數導出、重命名導出 (如 InternalFunc=ExportedName)、導出資料變數。 |
#pragma comment(lib, ...) | MSVC 專用指令,將庫檔名嵌入目標檔,連結器自動讀取,免去專案設定「附加相依性」。 |
dumpbin /exports | 檢視 DLL 實際導出符號的神器。解決 GetProcAddress 找不到符號的首選工具。 |
.lib 或 #pragma comment(lib) 路徑錯誤。顯式連結:GetProcAddress 參數名稱不匹配 (需用 dumpbin 核對)。__stdcall,呼叫端函數指標未聲明 __stdcall (預設為 __cdecl)。extern "C" + __stdcall。C# 用 [DllImport("DllDemo.dll", CallingConvention=CallingConvention.StdCall)];Python ctypes.WinDLL (預設 StdCall)。掌握 DLL 建立與呼叫全流程,是 Windows 系統程式開發、跨語言互操作、插件架構設計的核心基本功。