摘要:該文介紹了鉤子的功能、類(lèi)型、和安裝方法。在.NET平臺(tái)通過(guò)一個(gè)實(shí)例實(shí)現(xiàn)了全局鍵盤(pán)鉤子和全局鼠標(biāo)鉤子的安裝和使用。
關(guān)鍵詞:HOOK;全局鉤子;.NET
中圖分類(lèi)號(hào):TP311 文獻(xiàn)標(biāo)識(shí)碼:A文章編號(hào):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
在許多的應(yīng)用系統(tǒng)中,人們需要鎖定計(jì)算機(jī)系統(tǒng),以限制用戶的操作,例如,在屏幕保護(hù)程序中,需要鎖定屏幕,在用戶沒(méi)有輸入正確的密碼時(shí)不能使用系統(tǒng),以保護(hù)個(gè)人隱私,同樣,在計(jì)算機(jī)考試系統(tǒng)中,往往需要屏蔽一些特殊的功能,例如需要屏蔽CTRL+C和復(fù)制的功能,在網(wǎng)絡(luò)教學(xué)演示系統(tǒng)中,需要屏蔽鍵盤(pán)鼠標(biāo)的功能,防止用戶退出演示系統(tǒng)。要實(shí)現(xiàn)上述功能,用普通的軟件開(kāi)發(fā)方法是無(wú)法實(shí)現(xiàn)的,此時(shí),采用鉤子技術(shù)就可以解決這些問(wèn)題。本文首先詳細(xì)介紹了鉤子技術(shù),并利用鉤子技術(shù)實(shí)現(xiàn)了一個(gè)全局鼠標(biāo)鉤子和全局鍵盤(pán)鉤子。鉤子技術(shù)能應(yīng)用到網(wǎng)吧登錄系統(tǒng)、機(jī)房教學(xué)系統(tǒng)和屏幕保護(hù)系統(tǒng)等許多的應(yīng)用場(chǎng)合。具有很好的應(yīng)用前景。
消息傳遞是Windows操作系統(tǒng)獨(dú)有的一種機(jī)制,Windows程序的運(yùn)行是基于消息驅(qū)動(dòng)的,所有的消息被封裝成一系列的事件,開(kāi)放人員依賴于事件,可以方便的進(jìn)行軟件開(kāi)發(fā),但同時(shí)也限制了軟件的功能,因此引入了鉤子。鉤子是Windows系統(tǒng)中重要的系統(tǒng)接口,用它可以截獲并處理發(fā)送給其他應(yīng)用程序的消息,來(lái)完成一般應(yīng)用程序難以實(shí)現(xiàn)的功能。每當(dāng)特定的消息發(fā)出,在沒(méi)有到達(dá)目的程序之前,鉤子就先捕獲該消息,亦即鉤子函數(shù) (也稱鉤子子程)先得到控制權(quán)。這時(shí)即可以處理該消息,也可以不作處理而繼續(xù)傳遞該消息,還可以強(qiáng)制結(jié)束消息的傳遞。利用鉤子的這個(gè)特性,開(kāi)發(fā)人員可以截獲不希望發(fā)生的消息,如截獲的鼠標(biāo)消息不交還給系統(tǒng),鼠標(biāo)將失靈。利用這個(gè)思路,我們就可以設(shè)計(jì)出滿足需求的屏幕鎖定軟件等。
1 鉤子類(lèi)型
每種類(lèi)型的鉤子可以使應(yīng)用程序監(jiān)視不同類(lèi)型的系統(tǒng)消息。鉤子可以分為線程級(jí)鉤子和系統(tǒng)級(jí)鉤子,線程級(jí)鉤子只能監(jiān)視特定線程的事件消息,而系統(tǒng)級(jí)鉤子可以監(jiān)視整個(gè)系統(tǒng)的事件消息。
每一個(gè)鉤子都有一個(gè)與之相關(guān)聯(lián)的指針列表,稱之為鉤子鏈表,由系統(tǒng)來(lái)維護(hù)。這個(gè)鏈表的指針指向指定的函數(shù),也就是該鉤子的各個(gè)處理函數(shù)(回調(diào)函數(shù))。當(dāng)與指定的鉤子類(lèi)型關(guān)聯(lián)的消息發(fā)生時(shí),系統(tǒng)就把這個(gè)消息傳遞到鉤子子程。鉤子子程是一個(gè)應(yīng)用程序定義的回調(diào)函數(shù),用以監(jiān)視系統(tǒng)或某一特定類(lèi)型的事件,這些事件可以是與某一特定線程關(guān)聯(lián)的,也可以是系統(tǒng)中所有線程的事件。如果與特定線程相關(guān)聯(lián),這樣的鉤子就叫線程鉤子或局部鉤子,如果與系統(tǒng)中所有的線程相關(guān),這樣的鉤子就叫系統(tǒng)鉤子或全局鉤子。不同的鉤子對(duì)應(yīng)不同的系統(tǒng)消息,表一給出了部分鉤子的使用范圍和值,如果你要查看所有的鉤子類(lèi)型,請(qǐng)參考微軟的platformSDK中的winuser.h頭文件。
2 鉤子的安裝、使用和卸載
本文將以一個(gè)全局鍵盤(pán)鉤子和全局鼠標(biāo)鉤子的安裝和使用的實(shí)例,來(lái)介紹在.net平臺(tái)如何實(shí)現(xiàn)鉤子技術(shù),鉤子安裝后,將屏蔽鍵盤(pán)的功能,同時(shí)安裝的鼠標(biāo)鉤子將返回當(dāng)前鼠標(biāo)的位置并顯示在標(biāo)簽控件上。鉤子的安裝、卸載和調(diào)用鉤子鏈表中的下一個(gè)鉤子子程都有對(duì)應(yīng)的API函數(shù),為了使用這些API函數(shù),我們需要使用平臺(tái)調(diào)用的方法,調(diào)用相應(yīng)DLL中的API函數(shù)。首先我們需要引入System.Runtime.InteropServices這個(gè)名稱空間。同時(shí)在類(lèi)的成員聲明位置,需要聲明要使用的API函數(shù):
[DllImport(\"user32.dll\")]
//此函數(shù)用于安裝鉤子。
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport(\"user32.dll\")]
//此函數(shù)用于卸載鉤子。
public static extern bool UnhookWindowsHookEx(int idHook);
[DllImport(\"user32.dll\")]
//此函數(shù)用于調(diào)用鉤子鏈表的下一個(gè)鉤子。
public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
同時(shí)我們還要聲明如下的變量,用于存放鉤子值和鉤子類(lèi)型值:
static int hook = 0;
public const int WH_MOUSE_LL = 14;
public const int WH_KEYBOARD_LL=13;
為了使用鉤子回調(diào)函數(shù),我們定義一個(gè)委托類(lèi)型HookProc,利用此委托類(lèi)型定義兩個(gè)委托實(shí)例MouseHookProcedure和KeyBoardHookProcedure,用于代理鼠標(biāo)鉤子和鍵盤(pán)鉤子的回調(diào)函數(shù):
public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
HookProc MouseHookProcedure;
HookProc KeyBoardHookProcedure;
下邊定義鼠標(biāo)鉤子和鍵盤(pán)鉤子的回調(diào)函數(shù):
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;將直接屏蔽鼠標(biāo),鼠標(biāo)將不起作用。
}
}
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;//直接返回,鉤子不在往后傳遞,意味著屏蔽鍵盤(pán)的功能。
}
界面上加一個(gè)按鈕,在按鈕的Click事件中編寫(xiě)代碼:
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 = \"安裝全局鉤子\";
}
}
}
}
此段代碼實(shí)現(xiàn)的功能就是安裝和卸載鉤子,如果安裝了鼠標(biāo)鉤子,在鼠標(biāo)的移動(dòng)過(guò)程中,將在標(biāo)簽控件中顯示鼠標(biāo)的坐標(biāo)信息。如果安裝了鍵盤(pán)鉤子,鍵盤(pán)將失效,如果要實(shí)現(xiàn)其他功能,可以修改MouseHookProc和KeyBoardHookProc這兩個(gè)回調(diào)函數(shù)。在此段代碼中,SetWindowsHookEx的第三個(gè)參數(shù)如果是(IntPtr)0,第四個(gè)參數(shù)是AppDomain.GetCurrentThreadId()即當(dāng)前線程的ID,這樣安裝的鉤子將是線程鉤子 ,只對(duì)當(dāng)前線程起作用。為了是鉤子為全局鉤子,需要使第四個(gè)參數(shù)為0,同時(shí)第三個(gè)參數(shù)設(shè)置為當(dāng)前模塊的句柄,這樣安裝的鉤子將是全局鉤子,為了得到當(dāng)前模塊的句柄我們需要使用API函數(shù)GetModuleHandle(),因此需要導(dǎo)入kernel32.dll 中的GetModuleHandle函數(shù):
[DllImport(\"kernel32.dll\", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
最終我們?cè)?net平臺(tái)實(shí)現(xiàn)了全局鍵盤(pán)鉤子和全局鼠標(biāo)鉤子。程序運(yùn)行效果如圖2。
3 總結(jié)
該文在充分介紹windows平臺(tái)的運(yùn)行機(jī)制的基礎(chǔ)上,介紹了鉤子技術(shù)的應(yīng)用范圍和鉤子技術(shù)的實(shí)現(xiàn)方法,最終通過(guò)一個(gè)實(shí)例講解了全局鍵盤(pán)鉤子和全局鼠標(biāo)鉤子在.net平臺(tái)的實(shí)現(xiàn)。為將來(lái)將鉤子技術(shù)應(yīng)用于其他方面提供了思路。