摘要:該文介紹了鉤子的功能、類型、和安裝方法。在.NET平臺通過一個實例實現了全局鍵盤鉤子和全局鼠標鉤子的安裝和使用。
關鍵詞:HOOK;全局鉤子;.NET
中圖分類號:TP311 文獻標識碼:A文章編號:1009-3044(2009)35-9971-03
Global System Hooks in .NET
HUA Hui
(Jinling Institute of Technology, Nanjing 211169, China)
Abstract: This paper introduces Hook’s functions, types, and the installation method. An application instance is given to introduce loading and using of Global Hooks in .net platform.
Key words: HOOK; global hook; .NET
在許多的應用系統中,人們需要鎖定計算機系統,以限制用戶的操作,例如,在屏幕保護程序中,需要鎖定屏幕,在用戶沒有輸入正確的密碼時不能使用系統,以保護個人隱私,同樣,在計算機考試系統中,往往需要屏蔽一些特殊的功能,例如需要屏蔽CTRL+C和復制的功能,在網絡教學演示系統中,需要屏蔽鍵盤鼠標的功能,防止用戶退出演示系統。要實現上述功能,用普通的軟件開發方法是無法實現的,此時,采用鉤子技術就可以解決這些問題。本文首先詳細介紹了鉤子技術,并利用鉤子技術實現了一個全局鼠標鉤子和全局鍵盤鉤子。鉤子技術能應用到網吧登錄系統、機房教學系統和屏幕保護系統等許多的應用場合。具有很好的應用前景。
消息傳遞是Windows操作系統獨有的一種機制,Windows程序的運行是基于消息驅動的,所有的消息被封裝成一系列的事件,開放人員依賴于事件,可以方便的進行軟件開發,但同時也限制了軟件的功能,因此引入了鉤子。鉤子是Windows系統中重要的系統接口,用它可以截獲并處理發送給其他應用程序的消息,來完成一般應用程序難以實現的功能。每當特定的消息發出,在沒有到達目的程序之前,鉤子就先捕獲該消息,亦即鉤子函數 (也稱鉤子子程)先得到控制權。這時即可以處理該消息,也可以不作處理而繼續傳遞該消息,還可以強制結束消息的傳遞。利用鉤子的這個特性,開發人員可以截獲不希望發生的消息,如截獲的鼠標消息不交還給系統,鼠標將失靈。利用這個思路,我們就可以設計出滿足需求的屏幕鎖定軟件等。
1 鉤子類型
每種類型的鉤子可以使應用程序監視不同類型的系統消息。鉤子可以分為線程級鉤子和系統級鉤子,線程級鉤子只能監視特定線程的事件消息,而系統級鉤子可以監視整個系統的事件消息。
每一個鉤子都有一個與之相關聯的指針列表,稱之為鉤子鏈表,由系統來維護。這個鏈表的指針指向指定的函數,也就是該鉤子的各個處理函數(回調函數)。當與指定的鉤子類型關聯的消息發生時,系統就把這個消息傳遞到鉤子子程。鉤子子程是一個應用程序定義的回調函數,用以監視系統或某一特定類型的事件,這些事件可以是與某一特定線程關聯的,也可以是系統中所有線程的事件。如果與特定線程相關聯,這樣的鉤子就叫線程鉤子或局部鉤子,如果與系統中所有的線程相關,這樣的鉤子就叫系統鉤子或全局鉤子。不同的鉤子對應不同的系統消息,表一給出了部分鉤子的使用范圍和值,如果你要查看所有的鉤子類型,請參考微軟的platformSDK中的winuser.h頭文件。
2 鉤子的安裝、使用和卸載
本文將以一個全局鍵盤鉤子和全局鼠標鉤子的安裝和使用的實例,來介紹在.net平臺如何實現鉤子技術,鉤子安裝后,將屏蔽鍵盤的功能,同時安裝的鼠標鉤子將返回當前鼠標的位置并顯示在標簽控件上。鉤子的安裝、卸載和調用鉤子鏈表中的下一個鉤子子程都有對應的API函數,為了使用這些API函數,我們需要使用平臺調用的方法,調用相應DLL中的API函數。首先我們需要引入System.Runtime.InteropServices這個名稱空間。同時在類的成員聲明位置,需要聲明要使用的API函數:
[DllImport(\"user32.dll\")]
//此函數用于安裝鉤子。
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport(\"user32.dll\")]
//此函數用于卸載鉤子。
public static extern bool UnhookWindowsHookEx(int idHook);
[DllImport(\"user32.dll\")]
//此函數用于調用鉤子鏈表的下一個鉤子。
public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
同時我們還要聲明如下的變量,用于存放鉤子值和鉤子類型值:
static int hook = 0;
public const int WH_MOUSE_LL = 14;
public const int WH_KEYBOARD_LL=13;
為了使用鉤子回調函數,我們定義一個委托類型HookProc,利用此委托類型定義兩個委托實例MouseHookProcedure和KeyBoardHookProcedure,用于代理鼠標鉤子和鍵盤鉤子的回調函數:
public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
HookProc MouseHookProcedure;
HookProc KeyBoardHookProcedure;
下邊定義鼠標鉤子和鍵盤鉤子的回調函數:
public int MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode < 0)
{
return CallNextHookEx(hook, nCode, wParam, lParam);
}
else
{
label1.Text = \"x:\" + Cursor.Position.X + \",y:\" + Cursor.Position.Y;
return CallNextHookEx(hook, nCode, wParam, lParam);
//此處如果是 return 1;將直接屏蔽鼠標,鼠標將不起作用。
}
}
public int KeyBoardHookProc(int nCode,IntPtr wParam,IntPtr lParam)
{
if (nCode < 0)
return CallNextHookEx(hook, nCode, wParam, lParam);
else
//return CallNextHookEx(hook, nCode, wParam, lParam);
return 1;//直接返回,鉤子不在往后傳遞,意味著屏蔽鍵盤的功能。
}
界面上加一個按鈕,在按鈕的Click事件中編寫代碼:
private void button1_Click(object sender, EventArgs e)
{
bool ret;
if (hook == 0)
{
MouseHookProcedure = new HookProc(this.MouseHookProc);
KeyBoardHookProcedure = new HookProc(this.KeyBoardHookProc);
Process currProcess = Process.GetCurrentProcess();
hook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProcedure, GetModuleHandle(currProcess.MainModule.ModuleName), 0);
hook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyBoardHookProcedure, GetModuleHandle(currProcess.MainModule.ModuleName), 0);
if (hook == 0)
{
MessageBox.Show(\"安裝鉤子失敗!\");
return;
}
button1.Text = \"卸載全局鉤子\";
}
else
{
ret = UnhookWindowsHookEx(hook);
if (ret == 1)
{
MessageBox.Show(\"卸載鉤子失敗!\");
return;
}
hook = 0;
button1.Text = \"安裝全局鉤子\";
}
}
}
}
此段代碼實現的功能就是安裝和卸載鉤子,如果安裝了鼠標鉤子,在鼠標的移動過程中,將在標簽控件中顯示鼠標的坐標信息。如果安裝了鍵盤鉤子,鍵盤將失效,如果要實現其他功能,可以修改MouseHookProc和KeyBoardHookProc這兩個回調函數。在此段代碼中,SetWindowsHookEx的第三個參數如果是(IntPtr)0,第四個參數是AppDomain.GetCurrentThreadId()即當前線程的ID,這樣安裝的鉤子將是線程鉤子 ,只對當前線程起作用。為了是鉤子為全局鉤子,需要使第四個參數為0,同時第三個參數設置為當前模塊的句柄,這樣安裝的鉤子將是全局鉤子,為了得到當前模塊的句柄我們需要使用API函數GetModuleHandle(),因此需要導入kernel32.dll 中的GetModuleHandle函數:
[DllImport(\"kernel32.dll\", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
最終我們在.net平臺實現了全局鍵盤鉤子和全局鼠標鉤子。程序運行效果如圖2。
3 總結
該文在充分介紹windows平臺的運行機制的基礎上,介紹了鉤子技術的應用范圍和鉤子技術的實現方法,最終通過一個實例講解了全局鍵盤鉤子和全局鼠標鉤子在.net平臺的實現。為將來將鉤子技術應用于其他方面提供了思路。