本文整合多種 C++ Builder 實作共享資料方案:CreateFileMapping 共享記憶體、#pragma data_seg 命名資料段 (VC++ 風格)、BCB 專屬 #pragma option -zR/-zT + .def 區段屬性,並提供鍵盤/滑鼠 Hook 完整範本。
原理:建立「具名」檔案對應物件 (INVALID_HANDLE_VALUE),映射到各進程虛擬位址空間。**所有進程看到相同實體記憶體內容**。
// SharedMem.h
class TSharedMem {
HANDLE hMap_;
void* pView_;
DWORD size_;
bool isCreator_;
public:
TSharedMem(const char* name, DWORD size) : hMap_(0), pView_(0), size_(size), isCreator_(false) {
hMap_ = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, name);
isCreator_ = (GetLastError() != ERROR_ALREADY_EXISTS);
pView_ = MapViewOfFile(hMap_, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (isCreator_ && pView_) ZeroMemory(pView_, size); // 首次建立者清零
}
~TSharedMem() { if (pView_) UnmapViewOfFile(pView_); if (hMap_) CloseHandle(hMap_); }
void* Data() const { return pView_; }
bool Valid() const { return pView_ != NULL; }
bool IsCreator() const { return isCreator_; }
};
// KeyboardHook.cpp - DLL 專案
#include
#include
#pragma hdrstop
// 共享資料結構
struct THookData {
HHOOK hHook; // Hook 句柄 (所有進程共用同一值)
HWND hTargetWnd; // 主程式視窗句柄 (接收通知)
UINT uMsg; // 自訂通知訊息
// 可擴充:計數器、旗標、設定參數...
};
const char* SHARED_MEM_NAME = "Global\\MyKeyboardHook_SharedData_2024"; // Global\ 需管理員權限,否則 Session 範圍
TSharedMem* g_pShared = NULL;
HINSTANCE g_hInst = NULL;
// 標準匯出函式:安裝/移除 Hook
extern "C" __declspec(dllexport) BOOL __stdcall InstallHook(HWND hTarget, UINT uMsg) {
if (!g_pShared) g_pShared = new TSharedMem(SHARED_MEM_NAME, sizeof(THookData));
if (!g_pShared->Valid()) return FALSE;
THookData* pData = (THookData*)g_pShared->Data();
if (pData->hHook) return FALSE; // 已安裝
pData->hTargetWnd = hTarget;
pData->uMsg = uMsg;
pData->hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KeyboardHookProc, g_hInst, 0);
return pData->hHook != NULL;
}
extern "C" __declspec(dllexport) BOOL __stdcall UninstallHook() {
if (!g_pShared || !g_pShared->Valid()) return FALSE;
THookData* pData = (THookData*)g_pShared->Data();
if (pData->hHook) {
UnhookWindowsHookEx(pData->hHook);
pData->hHook = NULL;
return TRUE;
}
return FALSE;
}
// Hook 回呼
LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (!g_pShared || !g_pShared->Valid()) return CallNextHookEx(NULL, nCode, wParam, lParam);
THookData* pData = (THookData*)g_pShared->Data();
if (nCode == HC_ACTION) {
// wParam = 虛擬鍵碼, lParam = 掃描碼/旗標
if (pData->hTargetWnd && pData->uMsg)
PostMessage(pData->hTargetWnd, pData->uMsg, wParam, lParam);
}
return CallNextHookEx(pData->hHook, nCode, wParam, lParam);
}
// DLL 入口點:進程附加/分離時維護共享記憶體參考計數 (簡化版)
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*) {
g_hInst = hinst;
if (reason == DLL_PROCESS_DETACH) {
// 注意:此時不宜主動 UninstallHook,避免影響其他進程
// 由主程式明確呼叫 UninstallHook 後 FreeLibrary
if (g_pShared) { delete g_pShared; g_pShared = NULL; }
}
return 1;
}
BCB 4.0+ 支援類似 VC++ #pragma data_seg 的機制,透過編譯器指示詞變更資料區段名稱,連結器再透過 .def 設定區段屬性為 SHARED。
#pragma option -zRSHSEG // 將預設資料段改名為 SHSEG
#pragma option -zTSHCLASS // 將預設資料類別改名為 SHCLASS
ProjectName.def (純文字):
LIBRARY MyHookDll
SEGMENTS
SHSEG CLASS 'SHCLASS' SHARED
// MouseHook.cpp - DLL 專案
#pragma option -zRSHSEG
#pragma option -zTSHCLASS
#include
#pragma argsused
// 以下變數自動置於共享段 SHSEG
POINT g_MousePos = {0,0};
HHOOK g_hHook = NULL;
int g_LoadCount = 0;
HINSTANCE g_hInst = NULL;
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*) {
g_hInst = hinst;
if (reason == DLL_PROCESS_ATTACH) g_LoadCount++;
else if (reason == DLL_PROCESS_DETACH) g_LoadCount--;
return 1;
}
extern "C" __declspec(dllexport) void GetMouse(int& x, int& y, int& count) {
x = g_MousePos.x; y = g_MousePos.y; count = g_LoadCount;
}
LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode == HC_ACTION) {
MOUSEHOOKSTRUCT* p = (MOUSEHOOKSTRUCT*)lParam;
g_MousePos = p->pt; // 直接寫入共享記憶體
}
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
extern "C" __declspec(dllexport) void EnableHook() {
if (!g_hHook) g_hHook = SetWindowsHookEx(WH_MOUSE, (HOOKPROC)MouseHookProc, g_hInst, 0);
}
extern "C" __declspec(dllexport) void DisableHook() {
if (g_hHook) { UnhookWindowsHookEx(g_hHook); g_hHook = NULL; }
}
#include
#pragma hdrstop
#include "Main.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
// 靜態連結導入 (需 MouseHook.lib,或動態 LoadLibrary/GetProcAddress)
extern "C" __declspec(dllimport) void EnableHook();
extern "C" __declspec(dllimport) void DisableHook();
extern "C" __declspec(dllimport) void GetMouse(int&, int&, int&);
void __fastcall TForm1::Timer1Timer(TObject *Sender) {
int x, y, cnt;
GetMouse(x, y, cnt);
Label1->Caption = Format("滑鼠: (%d, %d) DLL載入數: %d", ARRAYOFCONST((x, y, cnt)));
}
void __fastcall TForm1::BtnEnableClick(TObject *Sender) { EnableHook(); }
void __fastcall TForm1::BtnDisableClick(TObject *Sender) { DisableHook(); }
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action) { DisableHook(); }
// VC++ 專用
#pragma data_seg("SHARED")
HHOOK g_hHook = NULL;
HWND g_hWnd = NULL;
#pragma data_seg()
#pragma comment(linker, "/section:SHARED,rws") // r=read, w=write, s=shared
BCB 連結器 (ILINK32) 不支援 #pragma comment(linker,...),故**不可用於 BCB**,需改用方案二。
| 特性 | CreateFileMapping | BCB #pragma -zR/-zT + .def | VC++ #pragma data_seg |
|---|---|---|---|
| 編譯器相容性 | 所有 (VC++, BCB, Delphi, MinGW...) | 僅 Borland C++ Builder 4.0+ | 僅 Visual C++ |
| 部署複雜度 | 無額外檔案,純 API | 需 .def 檔加入專案 | 無額外檔案,編譯器指示詞 |
| 共享範圍 | 可跨 Session (Global\)、跨使用者 | 同 Session 內所有載入 DLL 進程 | 同 Session 內所有載入 DLL 進程 |
| 初始化控制 | 精確 (IsCreator 旗標) | 依賴 DLL_PROCESS_ATTACH 首次載入 | 同左 |
| 除錯便利性 | 可用工具檢視共享記憶體 | 變數像普通全域變數,直接 Watch | 同左 |
| 建議適用場景 | 通用首選、跨語言、需精確控制 | BCB 純專案、追求程式碼簡潔、團隊熟悉 BCB 特性 | VC++ 專案 |
PostMessage、寫入共享記憶體、CallNextHookEx。**嚴禁** MessageBox、檔案 I/O、Sleep、鎖定 Mutex、呼叫可能阻塞的 API。InterlockedIncrement/Exchange 原子操作;複雜結構需在共享區放入 CRITICAL_SECTION (需 InitializeCriticalSectionAndSpinCount + CRITICAL_SECTION_NO_DEBUG_INFO 放入共享區) 或改用 CreateMutex 跨進程同步。FreeLibrary 只減少**自身**進程參考計數。系統在**所有**進程卸載 DLL 後才真正釋放記憶體。共享段/共享記憶體生命週期隨最後一個進程釋放而結束。Hook32.dll/Hook64.dll,主程式依自身位元數載入對應 DLL。
MyHookSolution/
├── HookShared/ # 共用標頭檔: 共享結構定義、訊息常數、TSharedMem 類別
├── KeyboardHookDll/ # BCB DLL 專案 (方案一: CreateFileMapping)
│ ├── KeyboardHook.cpp
│ └── KeyboardHook.def (可選,若用方案二)
├── MouseHookDll/ # BCB DLL 專案 (方案二: #pragma -zR/-zT)
│ ├── MouseHook.cpp
│ └── MouseHook.def # 必須: SEGMENTS SHSEG CLASS 'SHCLASS' SHARED
└── HookTestApp/ # BCB VCL EXE 專案
├── Main.cpp/.h/.dfm
└── 靜態連結 .lib 或動態 LoadLibrary
Windows Hook 是系統級開發的利器,亦是雙面刃。C++ Builder 提供了 CreateFileMapping 通用方案與 #pragma option -zR/-zT 專屬優雅方案兩條路徑。掌握共享記憶體機制、嚴守回呼效能鐵律、妥善處理 32/64 位元與 UAC 隔離,方能打造穩定、高效的全域 Hook 元件。