C++ windows 平台的 Hook
?
From:https://www.jianshu.com/p/1cbde2276752
Windows Hook(鉤子)函數(shù)詳解:https://wenku.baidu.com/view/fd9088aaf46527d3250ce059.html
?
環(huán)境:vs 2019,添加Windows.h頭文件。
核心函數(shù):SetWindowsHookEx(),UnhookWindowsHookEx(),CallNextHookEx()
你可能在其他api文檔看到SetWindowsHookExA、SetWindowsHookExW這兩個(gè)函數(shù),他們是編碼上的區(qū)別,A指ASCII,W指wide-char也就是unicode編碼,但系統(tǒng)已經(jīng)幫我們用宏處理好,只需調(diào)用 SetWindowsHookEx 就行了,自動(dòng)選擇對(duì)應(yīng)編碼那個(gè)函數(shù)。
Hook:建議先百度自行稍微了解
HINSTANCE、HMODULE、HANDLE、HWND是一樣的東西,都是各種 typedef 繞來(lái)繞去,區(qū)別只在于人類使用時(shí)附加的語(yǔ)義,實(shí)際上都是一串?dāng)?shù)字。下面就混著用了。
下面僅以最基本的控制臺(tái)應(yīng)用示例。
?
非常簡(jiǎn)單的一個(gè)例子,運(yùn)行后鼠標(biāo)移動(dòng)即可看到效果:
LRESULT CALLBACK mymouse(int nCode, WPARAM wParam, LPARAM lParam) {cout<<"yes"<<endl;return CallNextHookEx(NULL, nCode, wParam, lParam); }int main() {HHOOK mouseHook = SetWindowsHookEx(WH_MOUSE_LL, mymouse, 0, 0);MSG msg;while (GetMessage(&msg, NULL, NULL, NULL)){}UnhookWindowsHookEx(mouseHook);return 0; }?
1.mymouse函數(shù):
LRESULT:long result,實(shí)際就是個(gè)宏,當(dāng)long(長(zhǎng)整數(shù))就行。
CALLBACK:也是宏,實(shí)際上是__stdcall,函數(shù)修飾關(guān)鍵字,詳細(xì)自行可百度。
WPARAM、LPARAM:還是宏,當(dāng)整數(shù)就行。不同的值有不同含義,具體看msdn。
為什么這樣定義?
因?yàn)镾etWindowsHookEx()的第二個(gè)參數(shù)就是接受這樣的一個(gè)函數(shù)指針。簡(jiǎn)單了解使用函數(shù)指針
該函數(shù)返回0時(shí),消息繼續(xù)往下傳遞;返回1時(shí)消息不再往下傳遞。
為什么是return CallNextHookEx()而不是直接return 0或1?
SetWindowsHookEx()把這個(gè)鉤子放在Hook鏈頭,不調(diào)用這個(gè)方法其他鏈節(jié)點(diǎn)的Hook不執(zhí)行。另外該函數(shù)的第一個(gè)參數(shù)沒(méi)用,直接null就行。
2.HHOOK類型:
代表加進(jìn)去的鉤子,后面解除鉤子的函數(shù)UnhookWindowsHookEx(),參數(shù)就是這個(gè)的對(duì)象。
3.MSG、while:
為了不讓程序結(jié)束,用一個(gè)while卡住它。
為什么不用while(true)?
看第5點(diǎn)
4.SetWindowsHookEx函數(shù):
原型:
看到陌生的不用頭大,都是關(guān)鍵字和宏罷了,直接講4個(gè)參數(shù)。
idHook:一個(gè)int值,不同的值對(duì)應(yīng)不同的鉤子(鼠標(biāo)or鍵盤(pán)之類的)。vs中打出WH_后,看自動(dòng)補(bǔ)全顯示的名字,你就能了解了,不用死記硬背。WH就是window hook的意思。
lpfn:函數(shù)指針,類型就是mymouse函數(shù)那個(gè)類型。每當(dāng)鉤子獲取到消息,就會(huì)調(diào)用該函數(shù)指針。
hMod:用于幫助找到真正的地址,下面會(huì)講。
dwThreadId:DWORD也是一個(gè)宏,當(dāng)整數(shù)就行。這里指你要把鉤子掛到哪個(gè)線程中,所有進(jìn)程的所有線程都可以選擇,只不過(guò)其他進(jìn)程的線程不一定掛的上,需要其他手段,不作細(xì)說(shuō)。填0就是嘗試把所有線程都給掛上,成了所謂的全局鉤子(只是嘗試,拒絕掛鉤子的線程還是掛不上)。
當(dāng)掛鉤子失敗時(shí),該函數(shù)返回null。另外msdn講解參數(shù)這段一定要看:
lpfn [in]
Type: HOOKPROC
A pointer to the hook procedure. If the dwThreadId parameter is zero or specifies the identifier of a thread created by a different process, the lpfn parameter must point to a hook procedure in a DLL. Otherwise, lpfn can point to a hook procedure in the code associated with the current process.
如果dwThreadId指定的線程并不是由當(dāng)前進(jìn)程創(chuàng)建的,那么子程(就是那個(gè)函數(shù),如示例的mymouse)一定要寫(xiě)在dll里(動(dòng)態(tài)鏈接庫(kù))。
原因:由于lpfn是指針和邏輯地址的機(jī)制,當(dāng)前進(jìn)程的函數(shù)地址不適用于其他進(jìn)程。系統(tǒng)一般需要借助dll模塊以確定函數(shù)入口地址(low-level除外,只有此時(shí)可以不寫(xiě)dll)。
hMod [in]
Type: HINSTANCE
A handle to the DLL containing the hook procedure pointed to by the lpfn parameter. The hMod parameter must be set to NULL if the dwThreadId parameter specifies a thread created by the current process and if the hook procedure is within the code associated with the current process.
當(dāng) dwThreadId指定的線程是當(dāng)前進(jìn)程創(chuàng)建的線程 且 子程的代碼是在當(dāng)前進(jìn)程(非dll)寫(xiě)的,那么這個(gè)hMod必須填0(也就是null)。
值得一提,在dll中時(shí),這個(gè)參數(shù)要填為該dll的句柄,可以有兩種方法獲取來(lái)填:
(1)把入口DllMain的參數(shù)hModule(HMODULE和HINSTANCE一樣的)保存下來(lái),填進(jìn)去(推薦)
(2)通過(guò)函數(shù)GetModuleHandle(L"dll name")來(lái)獲取(加L代表為unicode編碼,不用加dll后綴)(不推薦)
絕大多數(shù)情況下會(huì)寫(xiě)成動(dòng)態(tài)鏈接庫(kù)(建議)。上面示例能算上特例,不寫(xiě)在dll中就要:首先是鉤子類型不是WH_MOUSE而是WH_MOUSE_LL(LL代表low-level,另一種回調(diào)機(jī)制,寫(xiě)在dll中則兩種都可以),不然沒(méi)反應(yīng),其次第三個(gè)參數(shù)hMod寫(xiě)成0。
5.GetMessage函數(shù):從線程消息隊(duì)列獲取消息,沒(méi)有消息將進(jìn)入等待態(tài)直至有消息被插入隊(duì)列
(1)系統(tǒng)獲取消息-----系統(tǒng)分發(fā)消息---(Hook鏈)-----消息抵達(dá)線程消息隊(duì)列。(這下搞懂第一點(diǎn)說(shuō)的消息是否繼續(xù)傳遞和CallNextHookEx了吧)
(2)我們示例的控制臺(tái)程序創(chuàng)建時(shí)默認(rèn)是沒(méi)有消息隊(duì)列的,但調(diào)用相關(guān)函數(shù)時(shí)就自動(dòng)創(chuàng)建消息隊(duì)列,如GetMessage函數(shù)
(3)子程是由系統(tǒng)通過(guò)回調(diào)機(jī)制,把執(zhí)行SetWindowsHookEx函數(shù)的線程叫回來(lái)去執(zhí)行的,也就是示例中的mymouse函數(shù)是由主線程去執(zhí)行的。因此,如果寫(xiě)成while(true),那么線程將無(wú)限運(yùn)行,系統(tǒng)沒(méi)有機(jī)制能夠中斷它的執(zhí)行來(lái)把它叫過(guò)來(lái)執(zhí)行子程。但是,調(diào)用GetMessage函數(shù)后,線程有了消息隊(duì)列,而且我們沒(méi)有往里面插入消息,線程就進(jìn)入等待態(tài),此時(shí)系統(tǒng)便能夠控制該線程,讓它去執(zhí)行子程。也就是說(shuō),我們需要把這個(gè)線程弄成等待態(tài)去讓系統(tǒng)獲取控制權(quán),所以你還可以使用while(true){ Sleep(100); }達(dá)到相同目的。
(4)由于上述原因,對(duì)于掛Hook的線程來(lái)說(shuō),這樣一個(gè)循環(huán)查詢消息隊(duì)列是有必要的。
(5)如果系統(tǒng)發(fā)現(xiàn)hook的子程長(zhǎng)期得不到執(zhí)行(你的線程正在繁忙),那么系統(tǒng)會(huì)自動(dòng)刪除掉這個(gè)hook。這里就要注意了,不要安排耗時(shí)任務(wù)在該線程上。
?
?
?
總結(jié)
以上是生活随笔為你收集整理的C++ windows 平台的 Hook的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: apt-get、apt、yum、dpkg
- 下一篇: oracle数据块调用存储过程,VC调用