如前面的文章所提到的,線程消息在模态循環中會丢失,因為消息分發器(Message Dispatcher)不知道應該如何分發此消息。但是,如果模态循環能支援的話,我們有一種方法可以在它們消失之前看到它們。
WH_MSGFILTER 消息鈎子可以用來接收傳遞給 CallMsgFilter 函數的消息。幸運的是,視窗管理器中的所有模态循環都使用 CallMsgFilter 來允許線程線上程消息丢失之前捕獲它們。 是以,這為我們提供了一種方法,可以在消息通過模态循環時對它們進行監控。
讓我們在上次編寫的程式中添加一個消息過濾器,看看消息是如何通過消息過濾器的。
但是,請注意,這是不是解決之前問題的正确方法。 我們在上一篇文章中說明了正确的解決方法。
我用錯誤的方式來說明消息過濾器,主要是因為它沒有被開發人員很好地了解。 (例如,消息過濾器的正當理由是,阻止菜單循環看到某些輸入消息。)
從上一個程式開始,在我們将 PostThreadMessage 更改為 PostMessage 之前,然後進行以下更改:
>> 請移步 topomel 檢視圖檔 <<
在這裡,我們線上程上安裝了一個消息過濾器鈎子,以便我們可以在消息通過模态循環時顯示它們。 code 參數告訴我們什麼類型的模态循環檢索到了消息; 我們在這裡忽略它,因為我們想對所有模态循環進行過濾。
運作這個程式并觀察蜂鳴器聲不再丢失,因為我們的消息過濾器有機會看到它們并對它們做出反應。
消息過濾器技巧依賴于所有模态循環,它們在分發它們之前,通過消息過濾器發送它們檢索到的消息。 如果你正在編寫要進入庫的代碼,并且你有一個模态循環,那麼你也應該在分發消息之前調用消息過濾器,以防你的代碼庫的使用者想要對消息做一些事情。
>> 請移步 topomel 檢視圖檔 <<
MSGF_MYLIBRARY 可以是一個任意值,你可以選擇并記錄在庫的頭檔案中。 在 commctrl.h 頭檔案中,我們會看到這樣的示例代碼:
>> 請移步 topomel 檢視圖檔 <<
上面這些是由外殼(Shell)公共控件庫中的模式循環調用的消息過濾器。
你可能會問一個問題,“為什麼使用消息過濾器挂鈎而不是 GetMessage 挂鈎?”
消息過濾器鈎子比 GetMessage 鈎子占用資源更少,因為它們僅在請求時調用,與 GetMessage 鈎子相反,GetMessage 鈎子為每個檢索到的消息調用。 消息過濾器鈎子還會告訴你哪個模态循環正在執行過濾,這樣就可以調整相應的動作。
消息過濾器鈎子的缺點是所有模式循環都需要記住調用 CallMsgFilter,将其作為其排程循環的一部分。
但這個,應該問題不大,一句代碼的事兒。
總結
早在研究 VNC 代碼的那個年代,我就知道 有Windows 消息鈎子這回事兒了。
奈何,因為當時水準不濟,一直沒能透徹了解它并真正地寫出一些代碼,也就擱置了。
通過今天的文章,我還是沒太能完全了解它,但是,這有助于提醒我:Windows 基礎設施中有很多需要我去發現的瑰寶。
這話真好:不是缺少美,是缺少發現美的眼睛。