摘要: 本文主要對鉤子這種特殊的Windows編程機制進行了討論并給出了鼠標(biāo)鉤子和鍵盤鉤子的具體實現(xiàn)方法。 關(guān)鍵詞: 鉤子;鼠標(biāo)鉤子;Visual C++ 鉤子概述 鉤子(Hook)是Windows消息處理機制的一個要點(Point),。應(yīng)用程序可以通過鉤子機制截獲處理Window消息或是其他一些特定事件,。同DOS中斷截獲處理機制類似,應(yīng)用程序可以在鉤子上設(shè)置多個鉤子函數(shù),,由其組成一個與鉤子相關(guān)聯(lián)的指向鉤子函數(shù)的指針列表(鉤子鏈表),。當(dāng)鉤子所監(jiān)視的消息出現(xiàn)時,Windows首先將其送到調(diào)用鏈表中所指向的第一個鉤子函數(shù)中,,鉤子函數(shù)將根據(jù)其各自的功能對消息進行監(jiān)視,、修改和控制,并在處理完成后把消息傳遞給下一鉤子函數(shù)直至到達鉤子鏈表的末尾,。在鉤子函數(shù)交出控制權(quán)后,被攔截的消息最終仍將交還給窗口處理函數(shù),。雖然鉤子函數(shù)對消息的過濾將會略加影響系統(tǒng)的運行效率,,但在很多場合下通過鉤子對消息的過濾處理可以完成一些其他方法所不能完成的特殊功能。 可以看出,,鉤子的本質(zhì)是一段用以處理系統(tǒng)消息或特定事件的函數(shù),,通過系統(tǒng)調(diào)用將其掛入到系統(tǒng)。鉤子的種類有很多,,每一種鉤子負責(zé)截獲并處理相應(yīng)的消息,。鉤子機制允許應(yīng)用程序截獲并處理發(fā)往指定窗口的消息或特定事件,其監(jiān)視的窗口即可以是本進程內(nèi)的也可以是由其他進程所創(chuàng)建的,。在特定的消息發(fā)出后,、達目的窗口前,鉤子程序擁有對其控制權(quán),,此時的鉤子函數(shù)除了可以對截獲的消息進行各種處理外,,甚至還可以強行終止消息的繼續(xù)傳遞。 對于多個鉤子的安裝,,最近安裝的鉤子將被放置于鉤子鏈的開始,,最早安裝的鉤子則放在最后,,在鉤子監(jiān)視的消息出現(xiàn)時,操作系統(tǒng)調(diào)用鏈表開始處的第一個鉤子函數(shù)進行處理,,也就是說最后加入的鉤子優(yōu)先獲得控制權(quán),。這里提到的鉤子函數(shù)必須是一個回調(diào)函數(shù),而且不能定義為類成員函數(shù),,只能是普通的C函數(shù),,如: LRESULT CALLBACK HookProc(int nCode ,WPARAM wParam,LPARAM lParam); 線程局部鉤子與系統(tǒng)全局鉤子 鉤子根據(jù)其對消息監(jiān)視范圍的不同而分為系統(tǒng)全局鉤子和線程局部鉤子兩大類,其中線程局部鉤子只能監(jiān)視本進程中某個指定的線程,,而全局鉤子則可對在當(dāng)前系統(tǒng)下運行的所有線程進行監(jiān)視,。顯然,線程鉤子可以看作是全局鉤子的一個子集,,全局鉤子雖然功能強大但同時實現(xiàn)起來也比較煩瑣:其鉤子函數(shù)的實現(xiàn)必須封裝在獨立的動態(tài)鏈接庫中才可以被各種相關(guān)聯(lián)的應(yīng)用程序所使用,。 雖然對于線程局部鉤子并不要求其象系統(tǒng)全局鉤子一樣必須放置于動態(tài)鏈接庫中,但是推薦的做法仍是將其放到動態(tài)鏈接庫中去實現(xiàn),。這樣的處理不僅能使鉤子為系統(tǒng)內(nèi)的多個進程所訪問,,同時也可以在系統(tǒng)中被直接調(diào)用。對于一個只供單進程訪問的鉤子,,還可以將其鉤子處理過程放在安裝鉤子的同一個線程內(nèi),。 鉤子的安裝與卸載 系統(tǒng)是通過調(diào)用位于鉤子鏈表最開始處的鉤子函數(shù)而進行消息攔截處理的,因此在設(shè)置鉤子時要把回調(diào)函數(shù)放置于鉤子鏈表的鏈?zhǔn)?,操作系統(tǒng)會使其首先被調(diào)用,。由函數(shù)SetWindowsHookEx()負責(zé)將回調(diào)函數(shù)放置于鉤子鏈表的開始位置。SetWindowsHookEx()函數(shù)原型聲明為:
其中,,參數(shù)idHook 指定了鉤子的類型,,可以使用的類型有以下13種:
參數(shù)lpfn為指向鉤子函數(shù)的指針,,也即回調(diào)函數(shù)的首地址;參數(shù)hMod標(biāo)識了鉤子處理函數(shù)所處模塊的句柄;參數(shù)dwThreadId 指定被監(jiān)視的線程,如果明確指定了某個線程的ID就只監(jiān)視該線程,,此時的鉤子即為線程鉤子;如果該參數(shù)被設(shè)置為0,,則表示此鉤子為監(jiān)視系統(tǒng)所有線程的全局鉤子。此函數(shù)在執(zhí)行完后將返回一個鉤子句柄,。 在SetWindowsHookEx()函數(shù)完成對鉤子的安裝后,,如果被監(jiān)視的事件發(fā)生,系統(tǒng)會立即調(diào)用位于相應(yīng)鉤子鏈表開始處的鉤子函數(shù)進行處理,,每一個鉤子函數(shù)在進行處理時都要考慮是否需要把事件傳遞給下一個鉤子處理函數(shù),。如果需要傳遞,就要調(diào)用函數(shù)CallNestHookEx()。盡管在理論上不調(diào)用CallNestHookEx()也并不算錯,,但在實際使用時還是強烈建議無論是否需要進行事件傳遞都要在過程的最后調(diào)用一次CallNextHookEx( ),,否則將會引起一些無法預(yù)知的系統(tǒng)行為或是系統(tǒng)鎖定。該函數(shù)將返回位于鉤子鏈表中的下一個鉤子處理過程的地址,,至于具體的返回值類型則要視所設(shè)置的鉤子類型而定,。CallNextHookEx( )的函數(shù)原型為:
其中,參數(shù)hhk為由SetWindowsHookEx()函數(shù)返回的當(dāng)前鉤子句柄;參數(shù)nCode為傳給鉤子過程的事件代碼;參數(shù)wParam和lParam 則為傳給鉤子處理函數(shù)的參數(shù)值,,其具體含義同設(shè)置的鉤子類型有關(guān),。 由于安裝鉤子對系統(tǒng)的性能有一定的影響,所以在鉤子使用完畢后應(yīng)及時將其卸載以釋放其所占資源,。釋放鉤子的函數(shù)為UnhookWindowsHookEx(),,該函數(shù)比較簡單只有一個參數(shù)用于指定此前由SetWindowsHookEx()函數(shù)所返回的鉤子句柄,原型聲明如下:
使用鼠標(biāo)鉤子 由于系統(tǒng)全局鉤子在功能上完全覆蓋了線程局部鉤子,,因此其實際使用范圍要遠比線程局部鉤子廣泛的多,。本節(jié)也由此著重對系統(tǒng)全局鉤子的使用進行介紹。 鼠標(biāo)鉤子是鉤子中比較常用也是使用比較簡單的一類鉤子,。下面給出的應(yīng)用示例將通過安裝鼠標(biāo)全局鉤子來捕獲鼠標(biāo)當(dāng)前所處窗口的窗口標(biāo)題,。由于本例程使用了全局鉤子,因此首先構(gòu)造全局鉤子的載體——動態(tài)鏈接庫,??紤]到 Win32 DLL與Win16 DLL存在的差別,在Win32環(huán)境下要在多個進程間共享數(shù)據(jù),,就必須采取一些措施將待共享的數(shù)據(jù)提取到一個獨立的數(shù)據(jù)段,,并通過def文件將其屬性設(shè)置為讀寫共享:
在完成上述準(zhǔn)備工作后,在動態(tài)庫輸出函數(shù)StartHook()中調(diào)用SetWindowsHookEx()函數(shù)完成對全局鼠標(biāo)鉤子的安裝,,設(shè)定鼠標(biāo)鉤子函數(shù)為MouseProc(),,安裝函數(shù)返回的鉤子句柄保存于變量glhHook中:
在鼠標(biāo)鉤子安裝完畢后,系統(tǒng)內(nèi)的任何鼠標(biāo)動作所發(fā)出的鼠標(biāo)消息均要經(jīng)過鉤子函數(shù)MouseProc()的攔截過濾處理,。在此進行的處理是通過獲取當(dāng)前鼠標(biāo)所在位置下的窗口句柄,,并以此進一步得到窗口標(biāo)題,。在處理完成后,,調(diào)用CallNextHookEx()函數(shù)將本事件傳遞到鉤子鏈表中的下一個鉤子函數(shù):
此動態(tài)鏈接庫還提供有輸出函數(shù)StopHook(),調(diào)用程序通過對此函數(shù)的調(diào)用完成對先前加載鉤子的卸載,。在此輸出函數(shù)內(nèi)則是通過UnhookWindowsHookEx()函數(shù)來卸載指定鉤子的:
通過編譯,、鏈接可以得到有關(guān)鼠標(biāo)全局鉤子的動態(tài)鏈接庫,在經(jīng)過調(diào)用程序?qū)ζ涞恼{(diào)用后,,可以實現(xiàn)對在當(dāng)前系統(tǒng)所有線程中的鼠標(biāo)消息的攔截處理,。在鉤子動態(tài)鏈接庫加載到進程后,只需調(diào)用輸出函數(shù)StartHook()安裝好全局鉤子即可對鼠標(biāo)消息進行攔截過濾,,在調(diào)用程序退出前調(diào)用輸出函數(shù)StopHook()卸載鉤子,。 使用鍵盤鉤子 鍵盤鉤子同鼠標(biāo)鉤子一樣,,也是一類較常用的鉤子。而且總的來說鍵盤鉤子的使用與鼠標(biāo)鉤子的使用也是比較類似的,,下面通過一個程序?qū)嵗齺韺︽I盤鉤子的使用進行介紹,,在此例程中通過設(shè)置鍵盤全局鉤子來記錄當(dāng)前系統(tǒng)輸入的字符,并將攔截到的字符按日期保存到文件,。 由于本例程也使用的系統(tǒng)全局鉤子,,因此由調(diào)用程序和動態(tài)鏈接庫程序兩部分組成。動態(tài)鏈接庫程序提供了兩個輸出函數(shù)InstallLaunchEv()和UnInstall(),,分別用于鍵盤鉤子的安裝與卸載:
在鉤子安裝函數(shù)中,,通過SetWindowsHookEx()設(shè)置了鍵盤鉤子函數(shù)LauncherHook()。在鍵盤事件發(fā)生后此鉤子函數(shù)將被調(diào)用,,通過SaveLog()函數(shù)將捕獲到的字符保存到文件:
至于調(diào)用程序則非常簡單,,只是在程序開始運行后和在程序退出前分別調(diào)用動態(tài)鏈接庫提供的鉤子設(shè)置函數(shù)和鉤子卸載函數(shù)即可。 鉤子的種類有很多,,從前面給出的鼠標(biāo)鉤子和鍵盤鉤子也可以看出其編寫過程是非常類似的,。通常情況下作為鉤子載體的動態(tài)鏈接庫要提供有兩個必要的輸出函數(shù)——鉤子安裝函數(shù)和鉤子卸載函數(shù)。在鉤子安裝函數(shù)中通過SetWindowsHookEx()為不同類型的鉤子設(shè)置相應(yīng)的鉤子函數(shù),,實質(zhì)性的工作也主要是在鉤子函數(shù)中完成的,,具體情況應(yīng)視鉤子的類型和要完成的功能而定。在鉤子卸載函數(shù)中則主要是對UnhookWindowsHookEx()的調(diào)用,,并輔以必要的保護性代碼,。 |
|