首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

C/C++ 靜態連結庫 (DLL) 建立與呼叫完整指南:從 __declspec 到 .def 檔案

     編輯:Java 相關知識

注意:原文章標題為「Java 工具類:字串操作與 MD5」,但內容實為 C/C++ DLL 靜態連結庫教學。本文依實際內容重寫。

動態連結庫 (DLL, Dynamic Link Library) 是 Windows 平台實現代碼複用、模組化、進程間共享記憶體的核心機制。本文詳述兩種建立 DLL 的方式(__declspec(dllexport) 與 .def 檔案),以及隱式/顯式兩種呼叫模式的完整流程。

一、基本概念

  • 靜態連結庫:此處指 Import Library (.lib),包含 DLL 導出符號的存根,連結時解析符號地址。
  • 動態連結庫:執行期載入的 .dll,多進程可共享單一記憶體副本。
  • 導出/導入:DLL 導出函數供外部呼叫;EXE/DLL 導入函數使用外部功能。

二、方式一:__declspec(dllexport/dllimport) 建立 DLL

1. 建立專案

Visual Studio → 新專案 → Win32 Dynamic-Link Library → 專案名 DllDemo。

2. 標頭檔 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 標準呼叫慣例
}

3. 實作檔 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;
}

4. 建置

編譯生成 DllDemo.dll、DllDemo.lib、DllDemo.h 三個核心產物。

三、方式二:模組定義檔 .def 建立 DLL

不修改原始碼,透過 .def 檔案聲明導出符號。

1. 刪除 DllDemo.h,移除 #include "DllDemo.h"

2. 新增 DllDemo.def

LIBRARY MyDll
EXPORTS
    Max @1
  • LIBRARY:DLL 內部名稱 (可選)。
  • EXPORTS:導出符號列表。
  • Max @1:導出函數 Max,序數為 1 (可選,但建議指定)。

3. 實作檔直接定義函數 (無需宏)

// DllDemo.cpp
extern "C" int __stdcall Max(int a, int b) {
    if (a == b) return 0;
    return (a > b) ? a : b;
}

4. 建置

同樣生成 .dll、.lib,但無標頭檔,呼叫端需自行聲明。

四、呼叫 DLL:隱式連結 vs 顯式連結

模式一:隱式連結 (Load-time Dynamic Linking) - 最常用

連結時期解析符號,執行期自動載入 DLL。

  1. 複製 DllDemo.dll、DllDemo.lib、DllDemo.h 到呼叫端專案目錄。
  2. 呼叫端標頭檔 Caller.h:
#define DllAPI __declspec(dllimport)
#pragma comment(lib, "DllDemo.lib") // 告訴連結器連結此 .lib

extern "C" {
    DllAPI int __stdcall Max(int a, int b);
}
  1. 呼叫端原始碼 Caller.cpp:
#include "Caller.h"
#include 

int main() {
    int val = Max(2, 9);
    printf("Max = %d\n", val); // 輸出: Max = 9
    return 0;
}

優點:程式碼簡潔,像呼叫本地函數一樣。缺點:啟動時若找不到 DLL 會直接報錯無法啟動。

模式二:顯式連結 (Run-time Dynamic Linking) - 彈性最高

執行期手動 LoadLibrary、GetProcAddress、FreeLibrary,無需 .lib 檔案。

  1. 複製 DllDemo.dll 到呼叫端目錄或系統路徑。
  2. 使用 dumpbin /exports DllDemo.dll 確認導出符號裝飾名稱 (關鍵步驟)。
// 輸出範例:
    1    0 00001000 _Max@8 = @ILT+10(_Max@8)  // __stdcall 裝飾: _FuncName@參數總位元組數
  1. 呼叫端完整實作:
#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 等)。
__stdcallWin32 API 標準呼叫慣例:參數由右至左壓棧、被呼叫者清理棧、函數名裝飾為 _Name@ByteCount。函數指標類型必須宣告為 __stdcall,否則棧平衡錯誤導致崩潰。
__declspec(dllexport)告訴編譯器將符號放入 DLL 導出表。配合 dllimport 宏切換,同一標頭檔服務建置與使用兩端。
.def 檔案模組定義檔,非侵入式導出聲明。支援序數導出、重命名導出 (如 InternalFunc=ExportedName)、導出資料變數。
#pragma comment(lib, ...)MSVC 專用指令,將庫檔名嵌入目標檔,連結器自動讀取,免去專案設定「附加相依性」。
dumpbin /exports檢視 DLL 實際導出符號的神器。解決 GetProcAddress 找不到符號的首選工具。

六、常見錯誤與排查

LNK2019 無法解析的外部符號
隱式連結:未包含 .lib 或 #pragma comment(lib) 路徑錯誤。顯式連結:GetProcAddress 參數名稱不匹配 (需用 dumpbin 核對)。
執行時 "無法找到 xxx.dll"
DLL 不在搜尋路徑:應用程式目錄、系統目錄、PATH 環境變數目錄。解決:複製 DLL 到 EXE 同目錄或安裝到系統目錄。
呼叫後崩潰/棧損壞
呼叫慣例不匹配:DLL 為 __stdcall,呼叫端函數指標未聲明 __stdcall (預設為 __cdecl)。
C#/Python 呼叫失敗
確保 DLL 導出使用 extern "C" + __stdcall。C# 用 [DllImport("DllDemo.dll", CallingConvention=CallingConvention.StdCall)];Python ctypes.WinDLL (預設 StdCall)。

掌握 DLL 建立與呼叫全流程,是 Windows 系統程式開發、跨語言互操作、插件架構設計的核心基本功。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved