DLL(動態連結庫)是 Windows 程式設計核心技術。本文系統總結 VC++ 中非 MFC DLL的完整開發流程:從專案建立、導出語法、兩種調用方式、DEF 檔應用,到跨語言(VB/C#)呼叫慣例與進階主題。
| 類型 | 關鍵字 | 連結時機 | 體積影響 | 更新部署 |
|---|---|---|---|---|
| 靜態庫 | 無特殊修飾 | 連結期 | 代碼複製入 EXE | 需重新編譯 EXE |
| 動態庫 | __declspec(dllexport/dllimport) | 載入期/運行期 | 共享單一副本 | 替換 DLL 即可(介面相容前提) |
// MyLib.h
#ifdef MYLIB_EXPORTS
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif
extern "C" MYLIB_API int add(int x, int y); // C 連結慣例,無名稱修飾
MYLIB_API int g_var = 10; // 導出變數
class MYLIB_API MyClass { public: void foo(); }; // 導出整個類別
; MyLib.def
LIBRARY "MyLib"
EXPORTS
add @1 ; 序數 1,可由 GetProcAddress(MAKEINTRESOURCE(1)) 調用
g_var DATA ; 導出變數需加 DATA
?foo@MyClass@@QAEXXZ ; C++ 修飾名(複雜,不推薦手寫)
命令列:link /EXPORT:add /EXPORT:g_var ...,較少直接使用。
MYLIB_API// MyLib.h
#pragma once
#ifdef MYLIB_EXPORTS
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif
extern "C" MYLIB_API int add(int x, int y);
MYLIB_API int g_value = 42;
class MYLIB_API Calculator {
public:
Calculator();
int multiply(int a, int b);
private:
int m_factor;
};
// MyLib.cpp
#include "MyLib.h"
#include <stdio.h>
extern "C" MYLIB_API int add(int x, int y) { return x + y; }
Calculator::Calculator() : m_factor(2) {}
int Calculator::multiply(int a, int b) { return a * b * m_factor; }
MyLib.dll、MyLib.lib(導入庫)、MyLib.h// Client.cpp
#include "MyLib.h"
#pragma comment(lib, "MyLib.lib") // 指定導入庫
int main() {
printf("add(2,3)=%d\n", add(2,3));
printf("g_value=%d\n", g_value);
Calculator calc;
printf("calc.multiply(3,4)=%d\n", calc.multiply(3,4));
return 0;
}
// ClientDyn.cpp
#include <windows.h>
#include <stdio.h>
typedef int (*PFN_ADD)(int, int);
int main() {
HMODULE hDll = LoadLibrary(_T("MyLib.dll"));
if (!hDll) { printf("LoadLibrary failed: %d\n", GetLastError()); return 1; }
PFN_ADD pAdd = (PFN_ADD)GetProcAddress(hDll, "add"); // 或 MAKEINTRESOURCE(1)
if (pAdd) printf("add(5,6)=%d\n", pAdd(5,6));
else printf("GetProcAddress failed\n");
FreeLibrary(hDll);
return 0;
}
VB/VBA/C#/Python 等預設使用 __stdcall(參數右→左入棧、被呼叫者清棧)。C/C++ 預設 __cdecl(呼叫者清棧)。
// 導出 __stdcall 函式供 VB/C# 呼叫
extern "C" __declspec(dllexport) int __stdcall add_stdcall(int x, int y) {
return x + y;
}
// DEF 檔指定序數
EXPORTS
add_stdcall @1
C# 呼叫範例:
[DllImport("MyLib.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "add_stdcall")]
public static extern int AddStdCall(int x, int y);
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason, LPVOID lpReserved) {
switch (ul_reason) {
case DLL_PROCESS_ATTACH: /* 單次初始化 */ break;
case DLL_THREAD_ATTACH: /* 執行緒附加 */ break;
case DLL_THREAD_DETACH: /* 執行緒分離 */ break;
case DLL_PROCESS_DETACH: /* 清理資源 */ break;
}
return TRUE;
}
禁忌:DllMain 內**不可**呼叫
LoadLibrary、CreateThread、同步物件、CRT 函式,否則極易死結。
// 所有進程共享同一份 g_shared
#pragma data_seg(".SHARED")
MYLIB_API int g_shared = 0;
#pragma data_seg()
#pragma comment(linker, "/SECTION:.SHARED,RWS")
// 每個執行緒擁有獨立副本
__declspec(thread) int tls_var = 0;
extern "C" __declspec(dllexport) + MYLIB_API 宏.h + .lib 供靜態連結;保留 LoadLibrary 彈性__stdcall + 純 C 介面**本文整理自原博客技術筆記,經結構化重寫發布