VC++深入詳解(18):鉤子函數

來源:互聯網
上載者:User

什麼是HOOK編程?
這得從Windows訊息機制說起:當在應用程式視窗內點擊滑鼠左鍵時,作業系統會感知這一事件,然後把訊息放到應用程式的訊息響應隊列中,應用程式通過GetMessage讀取訊息,然後通過DispatchMessage將訊息調度給作業系統,作業系統會調用在設計視窗類別時指定的應用程式視窗過程函數對訊息進行處理。
假如我們希望對某個特殊的訊息進行屏蔽,比如希望這個應用程式不響應斷行符號和空格訊息,就需要截獲所有訊息,然後進行判斷,如果是這兩種訊息,則將它們屏蔽掉。為了實現這個功能,我們可以安裝一個HOOK過程,在此過程中檢查,再決定是否允許存取該訊息。

我們先看代碼。在對話方塊應用程式的OnInitDialog中設定一個鉤子過程:

g_hMouse = SetWindowsHookEx(WH_MOUSE,//截獲滑鼠訊息MouseProc,//鉤子函數NULL,//指定的線程GetCurrentThreadId())//當前線程

其中g_hMouse是用來存放鉤子過程控制代碼的全域變數函數,而MouseProc則是與滑鼠訊息相對應的鉤子函數,如果這個函數返回非0值,則表示以對其進行了處理,系統將不會把這訊息傳遞給視窗過程函數了。我們這裡只是簡單地讓其返回1:

LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam,  LPARAM lParam){return 1;}

我們運行一下程式,發現當滑鼠移動到程式上時,滑鼠變成了等待狀態。無法點擊確定或者取消按鈕,只能通過按鍵來退出程式。
下面我們屏蔽鍵盤的空格鍵訊息:首先設定一個鉤子

g_hKeyboard = SetWindowsHookEx(WH_KEYBOARD,   KeyboardProc,   NULL,   GetCurrentThreadId());

然後在鉤子函數中對於空格鍵直接返回1

LRESULT CALLBACK KeyboardProc(  int code, WPARAM wParam,  LPARAM lParam ){if(VK_SPACE == wParam){return 1;}else{return CallNextHookEx(g_hKeyboard,code,wParam,lParam);}}

其中VK_SPACE是空格鍵對應的虛擬按鍵碼。在很久很久以前,各個公司生產的鍵盤是不一樣的,為了能讓Windows相容不同的鍵盤,微軟搞了一個虛擬按鍵碼,使得同一個按鍵(比如字母A),不論它在各種類型的鍵盤上的位置如何,作業系統都能通過驅動辨認出來。虛擬按鍵是以VK開頭的,我們也可以通過查看VK_SPACE的定義來看到其他的按鍵碼。如果你想在鉤子函數中處理這個訊息,那麼調用CallNextHookEx作為鉤子函數的傳回值。
假如我們想屏蔽Alt+F4鍵,那麼可以

if(VK_F4 == wParam && (1 == (lParam>>29 &1)))

如果lParam 的第29位為1,那麼表明Alt鍵按下。我們可以將lParam 右移29位,然後與1相與即可。

假如我們想實現屏蔽其他鍵,但是按F2鍵程式退出,那麼我們應該在按鍵響應中判斷是否為F2鍵,如果是就該發送退出訊息。可是發送訊息時,需要擷取視窗的控制代碼,這可怎麼辦好呢?無奈之下,我們只能使用一個全域控制代碼,並在OnInitDialog將其設為m_hWnd。
鉤子函數的內容改為

if(VK_F2 == wParam){::SendMessage(g_hWnd,WM_CLOSE,0,0);UnhookWindowsHookEx(g_hKeyboard);UnhookWindowsHookEx(g_hMouse);}return 1;

前面介紹的鉤子函數都只能屏蔽當前進程的主線程上滑鼠和鍵盤訊息,我們可以利用全域鉤子來屏蔽正在啟動並執行所有線程的滑鼠和鍵盤訊息。但這個鉤子函數必須在動態連結程式庫中。我們先建一個動態連結程式庫:

#include <windows.h>HHOOK g_hMouse = NULL;LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam){return 1;}void SetHook(){g_hMouse = SetWindowsHookEx(WH_MOUSE,//滑鼠訊息MouseProc,//鉤子函數GetModuleHandle("Hook"),//動態連結程式庫控制代碼0);//與所有線程相關聯}

並為其指定模組定義檔案,將匯出函數名設為SetHook,序號為2

LIBRARY CH_20_HOOkEXPORTSSetHook @2

然後,我們建立一個MFC對話方塊工程,在OnInitDialog中調用SetHook()函數(當然,不要忘記對其聲明:_declspec(dllimport) void SetHook();並在連結選項中增加對應的.lb)。然後build程式,運行,就會發現所有進程的滑鼠訊息都被屏蔽了。

下面我們在動態連結程式庫中增加一個鉤子函數,屏蔽鍵盤訊息。首先,我們不能屏蔽所有的鍵盤訊息,否則我們將無法退出程式,只能強者重新啟動了。我們可以為鍵盤訊息留一個F2鍵,當按下F2鍵時,程式將自動結束。程式的退出可以通過發送WM_CLOSE訊息來實現,但是發送訊息時需要獲得當前視窗的控制代碼,這可如何是好呢?我們可以把視窗的控制代碼當做參數傳遞進去,然後在dll中用一個全域變數接收,然後在鉤子函數中就能獲得這個控制代碼了:

#include <windows.h>HHOOK g_hMouse = NULL;HHOOK g_hKeyboard = NULL;HWND g_hWnd;LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam){return 1;}LRESULT CALLBACK KeyboardProc(int code,WPARAM wParam, LPARAM lParam ){if(VK_F2 == wParam){SendMessage(g_hWnd,WM_CLOSE,0,0);UnhookWindowsHookEx(g_hMouse);UnhookWindowsHookEx(g_hKeyboard);}return 1;}void SetHook(HWND hwnd){g_hWnd = hwnd;g_hMouse = SetWindowsHookEx(WH_MOUSE,//滑鼠訊息MouseProc,//鉤子函數GetModuleHandle("CH_20_Hook"),//動態連結程式庫控制代碼0);//與所有線程相關聯g_hKeyboard = SetWindowsHookEx(WH_KEYBOARD,   KeyboardProc,   GetModuleHandle("CH_20_Hook"),   0);}

當運行程式時,在視窗啟用時按下F2,程式就能退出,而當在別的視窗下按下F2,卻不能退出。這與我們之前對dll的理解有一些出入:之前提到,當dll被多個進程使用時,這些進程可以共用DLL的代碼和資料。因此dll中的全域變數g_hWnd應該是被所有進程所共用的。在其他進程下按下F2,也能夠退出。可事實上並不是這樣,這是為什麼呢?這是因為Windows採用了寫入時複製機制,當DLL被兩個進程共用時,如果第二個進程想要修改其中的資料,那麼他將會複製一份新的頁面。這樣做是為了安全著想,因為假設DLL中的變數時一個指標,第一個調用dll的進程修改了它的話,當第二個進程調用它時,指標可能已經指向了某個其他的地方。

如果非要讓全域控制代碼g_hWnd可以被共用的話,可以將它放到一個共用的節中:

#pragma data_seg("MySec")HWND g_hWnd = NULL;#pragma data_seg()#pragma comment(linker,"/section:MySec,RWS")

注意,共用的變數必須要初始化次時,用dumpbin -headers查看,可以看到:

SECTION HEADER #5
   MySec name
     104 virtual size
   37000 virtual address
    1000 size of raw data
   35000 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
D0000040 flags
         Initialized Data
         Shared
         Read Write

此時,在任意視窗下按F2,都能關閉程式了。
除了使用#pragma comment來指定節的屬性外,我們也可以在模組定義檔案中設定共用節的屬性:

SEGMENTSMySec READ WRITE SHARED

這裡的屬性就必須使用全稱了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.