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

C++ Builder 開發 Windows Hook 完全指南:鍵盤/滑鼠 Hook、共享記憶體、DLL 共享資料段實戰

     編輯:關於C++

前言:Hook 開發的三大核心挑戰

  1. 跨進程注入: 全域 Hook 必須置於 DLL,系統將 DLL 映射到所有目標進程位址空間。
  2. 資料共享: Hook 回呼在各進程獨立執行,全域變數**不共享** (每個進程一份副本)。需共享 Hook 句柄、目標視窗句柄、狀態旗標等關鍵資料。
  3. 執行緒安全與效能: 回呼執行於目標進程內容,任何阻塞/死鎖/例外會拖垮該進程甚至全系統輸入。

本文整合多種 C++ Builder 實作共享資料方案:CreateFileMapping 共享記憶體、#pragma data_seg 命名資料段 (VC++ 風格)、BCB 專屬 #pragma option -zR/-zT + .def 區段屬性,並提供鍵盤/滑鼠 Hook 完整範本。

方案一:CreateFileMapping 共享記憶體 (通用、穩健、跨語言)

原理:建立「具名」檔案對應物件 (INVALID_HANDLE_VALUE),映射到各進程虛擬位址空間。**所有進程看到相同實體記憶體內容**。

封裝類別:TSharedMem


// 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_; }
};

鍵盤 Hook DLL 完整實作 (KeyboardHook.dll)


// 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 專屬 #pragma option -zR/-zT + .def 共享資料段

BCB 4.0+ 支援類似 VC++ #pragma data_seg 的機制,透過編譯器指示詞變更資料區段名稱,連結器再透過 .def 設定區段屬性為 SHARED。

步驟詳解

  1. 原始檔開頭加入:
    #pragma option -zRSHSEG   // 將預設資料段改名為 SHSEG
    #pragma option -zTSHCLASS // 將預設資料類別改名為 SHCLASS
    
  2. 專案目錄新增 ProjectName.def (純文字):
    LIBRARY MyHookDll
    SEGMENTS
      SHSEG CLASS 'SHCLASS' SHARED
    
  3. 專案選項 → Linker → 將 .def 檔加入專案 (或在專案管理器右鍵 Add)。
  4. 此後該單元 (cpp) 宣告的全域/靜態變數即位於共享段,所有載入此 DLL 的進程**共享同一份實體記憶體**。

滑鼠 Hook 範例 (MouseHook.dll) - 共享段版


// 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; }
}

主程式呼叫端 (MouseHookApp.exe)


#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 + /SECTION (僅供參考,BCB 不直接支援)


// 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**,需改用方案二。

三大方案比較與選型建議

特性CreateFileMappingBCB #pragma -zR/-zT + .defVC++ #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++ 專案

Hook 開發黃金法則 (必讀)

  1. 回呼極速返回: 只做 PostMessage、寫入共享記憶體、CallNextHookEx。**嚴禁** MessageBox、檔案 I/O、Sleep、鎖定 Mutex、呼叫可能阻塞的 API。
  2. 共享資料同步: 多進程並發讀寫共享記憶體/共享段時,簡單資料 (旗標、句柄、計數器) 可用 InterlockedIncrement/Exchange 原子操作;複雜結構需在共享區放入 CRITICAL_SECTION (需 InitializeCriticalSectionAndSpinCount + CRITICAL_SECTION_NO_DEBUG_INFO 放入共享區) 或改用 CreateMutex 跨進程同步。
  3. DLL 卸載時機: 主程式 FreeLibrary 只減少**自身**進程參考計數。系統在**所有**進程卸載 DLL 後才真正釋放記憶體。共享段/共享記憶體生命週期隨最後一個進程釋放而結束。
  4. 64 位元支援: BCB 10.4+ 支援 Win64。32 位元 DLL 無法注入 64 位元進程。需分別建置 Hook32.dll/Hook64.dll,主程式依自身位元數載入對應 DLL。
  5. 安全性/完整性等級: 高完整性 (管理員) 進程不載入低完整性 DLL。主程式若需 Hook 系統進程,必須以管理員執行並簽署 DLL (或關閉 UAC/簽署驅動)。

完整專案結構建議

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 元件。

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