答案是肯定的。Windows 鉤子鏈設計為支援同類型多個鉤子共存,關鍵在於正確呼叫 CallNextHookEx 將控制權傳遞給下一個鉤子。
HHOOK hOldHook; // 全域變數保存舊鉤子控制代碼
hOldHook = SetWindowsHookEx(WH_CBT, MyCbtProc, hInstance, threadId);
LRESULT CALLBACK MyCbtProc(int code, WPARAM wp, LPARAM lp)
{
if (code == HCBT_CREATEWND) {
// 自訂處理...
}
return CallNextHookEx(hOldHook, code, wp, lp); // 必須呼叫
}
MFC 利用 CBT 鉤子監視視窗建立過程,在視窗收到任何訊息前將 CWnd 物件附加到 HWND 並完成子類化。
CWnd::CreateEx 呼叫 AfxHookWindowCreate(this),安裝 CBT 鉤子 _AfxCbtFilterHook,並在執行緒狀態 _AFX_THREAD_STATE 中保存 m_pWndInit = this。::CreateWindowEx 建立視窗。WM_CREATE 等訊息前,以 HCBT_CREATEWND 呼叫 CBT 鉤子。_AfxCbtFilterHook 取得 m_pWndInit 指標,呼叫 Attach 並設定 AfxWndProc 完成子類化。::CreateWindowEx 返回,CWnd::CreateEx 呼叫 AfxUnhookWindowCreate 移除鉤子並清空 m_pWndInit。因為 ::CreateWindowEx 在返回前已發送 WM_GETMINMAXINFO、WM_NCCREATE、WM_CREATE 等訊息。若不在訊息發送前附加 CWnd,這些早期訊息將遺失。CBT 鉤子正是在建立視窗後、發送訊息前的唯一切入點。
CallNextHookEx,即可安裝任意數量同類型鉤子。SetWindowsHookEx 無 void* 參數,唯一方式是使用執行緒區域儲存(TLS)或全域變數。MFC 使用執行緒狀態結構達成此目的。.NET 無 AfxGetInstanceHandle 對應物。正確做法:
Type t = myObj.GetType(); // 或 typeof(MyApp)
Module m = t.Module;
IntPtr h = Marshal.GetHINSTANCE(m);
必須使用「呼叫模組中定義的型別」,否則會取得錯誤模組(如 String 會取得 mscorlib)。
String 為不可變型別,修改字串實為建立新實例。對應 C# ref string,C++/CLI 可用 String^%(追蹤參考)或 String*&(指標參考):
int ErrorMessage(String^% msg) { msg = "new value"; return -1; }
若需從資源檔載入字串,.NET 採用 .resx + ResourceManager 機制;若暫時沿用 RC 檔,可先以 CString 載入再轉換:
CString s; HINSTANCE h = GetModuleHandle(_T("MyLib.dll")); s.LoadString(h, id);
String^ managed = gcnew String(s);
理解鉤子鏈、MFC 視窗建立機制、托管/非托管互操作細節,能幫助開發者正確使用進階 Windows 功能並避免常見陷阱。