Windows Hooks

作者: 蔡煥麟
日期: Feb-16-1999


什麼是 hook ?

Hook 一般譯為掛勾, 而其作用與之前 DOS 作業環境下的常駐程式攔截中斷的做法有異曲同工之妙。Win32 SDK help 如是說:「Hook 是視窗訊息處理機制中的一個點, 而應用程式可以在該處安插一個程序來監控系統及行程之間的訊息流動」。
此處所說的程序, 是一個 callback 函式, 稱為掛勾程序 (hook procedures)。

關於 hook 的兩三事

掛勾串列與掛勾程序

Windows 系統提供數種不同類型的掛勾, 例如滑鼠掛勾, 鍵盤掛勾...等。 不同的應用程式可能會設置相同類型的掛勾, 於是便形成了掛勾串列 (hook chains), 而掛勾串列其實就是一串指向掛勾程序的指標, 這些指標串列由視窗作業系統所維護, 但是每個掛勾程序必須遵守遊戲規則才能讓整個掛勾串列的所有掛勾程序都被執行到, 這個規則就是: 每個掛勾程序在處理完自己的事情之後(或之前) 必須呼叫 CallNextHookEx。這個規則不是強制性的, 但你絕對應該遵守它, 你也不希望自己的掛勾程序被後來安裝的掛勾所破壞, 不是嗎?

你可以透過 API 函式 SetWindowsHookEx 來安裝掛勾程序, 每當你呼叫了這個函式, Windows 會把新設置的掛勾程序置於掛勾串列的起始位置, 也就是說, 愈晚安裝的掛勾其優先權越高。以下是 SetWindowsHookEx 的原型宣告:

HHOOK SetWindowsHookEx(
  int idHook,       // 欲安裝的掛勾型態
  HOOKPROC lpfn,    // 掛勾程序的位址
  HINSTANCE hMod,   // 掛勾程序所在的 DLL handle
  DWORD dwThreadId  // 指定掛勾要安裝至哪個執行緒 
);

其中 dwThreadId 如果是 0 的話, 表示要安裝一個 system hook, 參數 lpfn 所指向的掛勾程序就必須存在於 hMod 所代表的 DLL 中, 相反的, 如果 dwThreadId 不為 0, 那麼這是一個 thread specific hook, 參數 hMod 必須為 NULL。

掛勾程序的原型宣告為:

LRESULT CALLBACK HookProc(
  int nCode,
  WPARAM wParam,
  LPARAM lParam
);

其中每個參數依照掛勾種類的不同各有不同的作用, 這些資訊在 Win32 SDK Help 中都有詳細的說明, 這裡就不贅述。

注意: Hook procedure 呼叫慣例必須為 stdcall, 而宣告 CALLBACK 或 WINAPI 就等於是
stdcall。

實戰演練

了解 hook 的工作原理之後, 應該是自己動手做的時間了, 這裡有一個以 C++Builder3 所撰寫的簡單的範例, 下載回去並將壓縮檔解開之後, 你可以在 C++Builder 的 IDE 中開啟 MakeAll.bpg, 這個 Project Group 包含兩個專案, 一個是 KbdHook.bpr, 這個是鍵盤掛勾的 DLL 專案檔, 先編譯這個專案以產生 KbdHook.DLL。另一個 TestHook.bpr 則是用來測試 KbdHook.DLL。

下載 KbdHook.zip (see Readme.txt first)

附帶一提: 當你要建立一個新的 DLL project 時, 可能會用 File|New|DLL 方式來建立, 而如果該專案中不需要使用VCL 元件, 我的建議是以 File|New|Console Wizard 的方式來建立比較好。