編譯器鉤子
『壹』 vuejs什麼時候使用鉤子函數
在之前基礎上對組件進行了生命周期的加工(初始化、獲取資源、渲染、更新、銷毀等),理順了組件的各個階段,有助於對組件實現(從初始化到銷毀)的理解。 並且藉助於組件各個階段的鉤子可以對組件有更好的利用和擴展。
『貳』 系統鉤子是什麼
一、 介紹
本文將討論在.NET應用程序中全局系統鉤子的使用。為此,我開發了一個可重用的類庫並創建一個相應的示常式序(見下圖)。
你可能注意到另外的關於使用系統鉤子的文章。本文與之類似但是有重要的差別。這篇文章將討論在.NET中使用全局系統鉤子,而其它文章僅討論本地系統鉤子。這些思想是類似的,但是實現要求是不同的。
二、 背景
如果你對Windows系統鉤子的概念不熟悉,讓我作一下簡短的描述:
・一個系統鉤子允許你插入一個回調函數-它攔截某些Windows消息(例如,滑鼠相聯系的消息)。
・一個本地系統鉤子是一個系統鉤子-它僅在指定的消息由一個單一線程處理時被調用。
・一個全局系統鉤子是一個系統鉤子-它當指定的消息被任何應用程序在整個系統上所處理時被調用。
已有若干好文章來介紹系統鉤子概念。在此,不是為了重新收集這些介紹性的信息,我只是簡單地請讀者參考下面有關系統鉤子的一些背景資料文章。如果你對系統鉤子概念很熟悉,那麼你能夠從本文中得到你能夠得到的任何東西。
・關於MSDN庫中的鉤子知識。
・Dino Esposito的《Cutting Edge-Windows Hooks in the .NET Framework》。
・Don Kackman的《在C#中應用鉤子》。
本文中我們要討論的是擴展這個信息來創建一個全局系統鉤子-它能被.NET類所使用。我們將用C#和一個DLL和非託管C++來開發一個類庫-它們一起將完成這個目標。
三、 使用代碼
在我們深入開發這個庫之前,讓我們快速看一下我們的目標。在本文中,我們將開發一個類庫-它安裝全局系統鉤子並且暴露這些由鉤子處理的事件,作為我們的鉤子類的一個.NET事件。為了說明這個系統鉤子類的用法,我們將在一個用C#編寫的Windows表單應用程序中創建一個滑鼠事件鉤子和一個鍵盤事件鉤子。
這些類庫能用於創建任何類型的系統鉤子,其中有兩個預編譯的鉤子-MouseHook和KeyboardHook。我們也已經包含了這些類的特定版本,分別稱為MouseHookExt和KeyboardHookExt。根據這些類所設置的模型,你能容易構建系統鉤子-針對Win32 API中任何15種鉤子事件類型中的任何一種。另外,這個完整的類庫中還有一個編譯的HTML幫助文件-它把這些類歸檔化。請確信你看了這個幫助文件-如果你決定在你的應用程序中使用這個庫的話。
MouseHook類的用法和生命周期相當簡單。首先,我們創建MouseHook類的一個實例。
mouseHook = new MouseHook();//mouseHook是一個成員變數
接下來,我們把MouseEvent事件綁定到一個類層次的方法上。
mouseHook.MouseEvent+=new MouseHook.MouseEventHandler(mouseHook_MouseEvent);
// ...
private void mouseHook_MouseEvent(MouseEvents mEvent, int x, int y){
string msg =string.Format("滑鼠事件:{0}:({1},{2}).",mEvent.ToString(),x,y);
AddText(msg);//增加消息到文本框
}
為開始收到滑鼠事件,簡單地安裝下面的鉤子即可。
mouseHook.InstallHook();
為停止接收事件,只需簡單地卸載這個鉤子。
mouseHook.UninstallHook();
你也可以調用Dispose來卸載這個鉤子。
在你的應用程序退出時,卸載這個鉤子是很重要的。讓系統鉤子一直安裝著將減慢系統中的所有的應用程序的消息處理。它甚至能夠使一個或多個進程變得很不穩定。因此,請確保在你使用完鉤子時一定要移去你的系統鉤子。我們確定在我們的示例應用程序會移去該系統鉤子-通過在Form的Dispose方法中添加一個Dispose調用。
protected override void Dispose(bool disposing) {
if (disposing) {
if (mouseHook != null) {
mouseHook.Dispose();
mouseHook = null;
}
// ...
}
}
使用該類庫的情況就是如此。該類庫中有兩個系統鉤子類並且相當容易擴充。
四、 構建庫
這個庫共有兩個主要組件。第一部分是一個C#類庫-你可以直接使用於你的應用程序中。該類庫,反過來,在內部使用一個非託管的C++ DLL來直接管理系統鉤子。我們將首先討論開發該C++部分。接下來,我們將討論怎麼在C#中使用這個庫來構建一個通用的鉤子類。就象我們討論C++/C#交互一樣,我們將特別注意C++方法和數據類型是怎樣映射到.NET方法和數據類型的。
你可能想知道為什麼我們需要兩個庫,特別是一個非託管的C++ DLL。你還可能注意到在本文的背景一節中提到的兩篇參考文章,其中並沒有使用任何非託管的代碼。為此,我的回答是,"對!這正是我寫這篇文章的原因"。當你思考系統鉤子是怎樣實際地實現它們的功能時,我們需要非託管的代碼是十分重要的。為了使一個全局的系統鉤子能夠工作,Windows把你的DLL插入到每個正在運行的進程的進程空間中。既然大多數進程不是.NET進程,所以,它們不能直接執行.NET裝配集。我們需要一種非託管的代碼代理- Windows可以把它插入到所有將要被鉤住的進程中。
首先是提供一種機制來把一個.NET代理傳遞到我們的C++庫。這樣,我們用C++語言定義下列函數(SetUserHookCallback)和函數指針(HookProc)。
int SetUserHookCallback(HookProc userProc, UINT hookID)
typedef void (CALLBACK *HookProc)(int code, WPARAM w, LPARAM l)
SetUserHookCallback的第二個參數是鉤子類型-這個函數指針將使用它。現在,我們必須用C#來定義相應的方法和代理以使用這段代碼。下面是我們怎樣把它映射到C#。
private static extern SetCallBackResults
SetUserHookCallback(HookProcessedHandler hookCallback, HookTypes hookType)
protected delegate void HookProcessedHandler(int code, UIntPtr wparam, IntPtr lparam)
public enum HookTypes {
JournalRecord = 0,
JournalPlayback = 1,
// ...
KeyboardLL = 13,
MouseLL = 14
};
首先,我們使用DllImport屬性導入SetUserHookCallback函數,作為我們的抽象基鉤子類SystemHook的一個靜態的外部的方法。為此,我們必須映射一些外部數據類型。首先,我們必須創建一個代理作為我們的函數指針。這是通過定義上面的HookProcessHandler 來實現的。我們需要一個函數,它的C++簽名為(int,WPARAM,LPARAM)。在Visual Studio .NET C++編譯器中,int與C#中是一樣的。也就是說,在C++與C#中int就是Int32。事情並不總是這樣。一些編譯器把C++ int作為Int16對待。我們堅持使用Visual Studio .NET C++編譯器來實現這個工程,因此,我們不必擔心編譯器差別所帶來的另外的定義。
接下來,我們需要用C#傳遞WPARAM和LPARAM值。這些確實是指針,它們分別指向C++的UINT和LONG值。用C#來說,它們是指向uint和int的指針。如果你還不確定什麼是WPARAM,你可以通過在C++代碼中單擊右鍵來查詢它,並且選擇"Go to definition"。這將會引導你到在windef.h中的定義。
//從windef.h:
typedef UINT_PTR WPARAM;
typedef LONG_PTR LPARAM;
因此,我們選擇System.UIntPtr和System.IntPtr作為我們的變數類型-它們分別相應於WPARAM和LPARAM類型,當它們使用在C#中時。
現在,讓我們看一下鉤子基類是怎樣使用這些導入的方法來傳遞一個回叫函數(代理)到C++中-它允許C++庫直接調用你的系統鉤子類的實例。首先,在構造器中,SystemHook類創建一個到私有方法InternalHookCallback的代理-它匹配HookProcessedHandler代理簽名。然後,它把這個代理和它的HookType傳遞到C++庫以使用SetUserHookCallback方法來注冊該回叫函數,如上面所討論的。下面是其代碼實現:
public SystemHook(HookTypes type){
_type = type;
_processHandler = new HookProcessedHandler(InternalHookCallback);
SetUserHookCallback(_processHandler, _type);
}
InternalHookCallback的實現相當簡單。InternalHookCallback在用一個catch-all try/catch塊包裝它的同時僅傳遞到抽象方法HookCallback的調用。這將簡化在派生類中的實現並且保護C++代碼。記住,一旦一切都准備妥當,這個C++鉤子就會直接調用這個方法。
[MethodImpl(MethodImplOptions.NoInlining)]
private void InternalHookCallback(int code, UIntPtr wparam, IntPtr lparam){
try { HookCallback(code, wparam, lparam); }
catch {}
}
我們已增加了一個方法實現屬性-它告訴編譯器不要內聯這個方法。這不是可選的。至少,在我添加try/catch之前是需要的。看起來,由於某些原因,編譯器在試圖內聯這個方法-這將給包裝它的代理帶來各種麻煩。然後,C++層將回叫,而該應用程序將會崩潰。
現在,讓我們看一下一個派生類是怎樣用一個特定的HookType來接收和處理鉤子事件。下面是虛擬的MouseHook類的HookCallback方法實現:
protected override void HookCallback(int code, UIntPtr wparam, IntPtr lparam){
if (MouseEvent == null) { return; }
int x = 0, y = 0;
MouseEvents mEvent = (MouseEvents)wparam.ToUInt32();
switch(mEvent) {
case MouseEvents.LeftButtonDown:
GetMousePosition(wparam, lparam, ref x, ref y);
break;
// ...
}
MouseEvent(mEvent, new Point(x, y));
}
首先,注意這個類定義一個事件MouseEvent-該類在收到一個鉤子事件時激發這個事件。這個類在激發它的事件之前,把數據從WPARAM和 LPARAM類型轉換成.NET中有意義的滑鼠事件數據。這樣可以使得類的消費者免於擔心解釋這些數據結構。這個類使用導入的 GetMousePosition函數-我們在C++ DLL中定義的用來轉換這些值。為此,請看下面幾段的討論。
在這個方法中,我們檢查是否有人在聽這一個事件。如果沒有,不必繼續處理這一事件。然後,我們把WPARAM轉換成一個MouseEvents枚舉類型。我們已小心地構造了MouseEvents枚舉來准確匹配它們在C ++中相應的常數。這允許我們簡單地把指針的值轉換成枚舉類型。但是要注意,這種轉換即使在WPARAM的值不匹配一個枚舉值的情況下也會成功。 mEvent的值將僅是未定義的(不是null,只是不在枚舉值范圍之內)。為此,請詳細分析System.Enum.IsDefined方法。
接下來,在確定我們收到的事件類型後,該類激活這個事件,並且通知消費者滑鼠事件的類型及在該事件過程中滑鼠的位置。
最後注意,有關轉換WPARAM和LPARAM值:對於每個類型的事件,這些變數的值和意思是不同的。因此,在每一種鉤子類型中,我們必須區別地解釋這些值。我選擇用C++實現這種轉換,而不是盡量用C#來模仿復雜的C++結構和指針。例如,前面的類就使用了一個叫作GetMousePosition的 C++函數。下面是C++ DLL中的這個方法:
bool GetMousePosition(WPARAM wparam, LPARAM lparam, int amp; x, int amp; y) {
MOUSEHOOKSTRUCT * pMouseStruct = (MOUSEHOOKSTRUCT *)lparam;
x = pMouseStruct->pt.x;
y = pMouseStruct->pt.y;
return true;
}
不是盡量映射MOUSEHOOKSTRUCT結構指針到C#,我們簡單地暫時把它回傳到C++層以提取我們需要的值。注意,因為我們需要從這個調用中返回一些值,我們把我們的整數作為參考變數傳遞。這直接映射到C#中的int*。但是,我們可以重載這個行為,通過選擇正確的簽名來導入這個方法。
private static extern bool InternalGetMousePosition(UIntPtr wparam,IntPtr lparam, ref int x, ref int y)
通過把integer參數定義為ref int,我們得到通過C++參照傳遞給我們的值。如果我們想要的話,我們還可以使用out int。
五、 限制
一些鉤子類型並不適合實現全局鉤子。我當前正在考慮解決辦法-它將允許使用受限制的鉤子類型。到目前為止,不要把這些類型添加回該庫中,因為它們將導致應用程序的失敗(經常是系統范圍的災難性失敗)。下一節將集中討論這些限制背後的原因和解決辦法。
HookTypes.CallWindowProcere
HookTypes.CallWindowProret
HookTypes.ComputerBasedTraining
HookTypes.Debug
HookTypes.ForegroundIdle
HookTypes.JournalRecord
HookTypes.JournalPlayback
HookTypes.GetMessage
HookTypes.SystemMessageFilter
六、 兩種類型的鉤子
在本節中,我將盡量解釋為什麼一些鉤子類型被限制在一定的范疇內而另外一些則不受限制。如果我使用有點偏差術語的話,請原諒我。我還沒有找到任何有關這部分題目的文檔,因此,我編造了我自己的詞彙。另外,如果你認為我根本就不對,請告訴我好了。
當Windows調用傳遞到SetWindowsHookEx()的回調函數時它們會因不同類型的鉤子而被區別調用。基本上有兩種情況:切換執行上下文的鉤子和不切換執行上下文的鉤子。用另一種方式說,也就是,在放鉤子的應用程序進程空間執行鉤子回調函數的情況和在被鉤住的應用程序進程空間執行鉤子回調函數的情況。
鉤子類型例如滑鼠和鍵盤鉤子都是在被Windows調用之前切換上下文的。整個過程大致如下:
1. 應用程序X擁有焦點並執行。
2. 用戶按下一個鍵。
3. Windows從應用程序X接管上下文並把執行上下文切換到放鉤子的應用程序。
4. Windows用放鉤子的應用程序進程空間中的鍵消息參數調用鉤子回調函數。
5. Windows從放鉤子的應用程序接管上下文並把執行上下文切換回應用程序X。
6. Windows把消息放進應用程序X的消息排隊。
7. 稍微一會兒之後,當應用程序X執行時,它從自己的消息排隊中取出消息並且調用它的內部按鍵(或松開或按下)處理器。
8. 應用程序X繼續執行...
例如CBT鉤子(window創建,等等。)的鉤子類型並不切換上下文。對於這些類型的鉤子,過程大致如下:
1. 應用程序X擁有焦點並執行。
2. 應用程序X創建一個窗口。
3. Windows用在應用程序X進程空間中的CBT事件消息參數調用鉤子回調函數。
4. 應用程序X繼續執行...
這應該說明了為什麼某種類型的鉤子能夠用這個庫結構工作而一些卻不能。記住,這正是該庫要做的。在上面第4步和第3步之後,分別插入下列步驟:
1. Windows調用鉤子回調函數。
2. 目標回調函數在非託管的DLL中執行。
3. 目標回調函數查找它的相應託管的調用代理。
4. 託管代理被以適當的參數執行。
5. 目標回調函數返回並執行相應於指定消息的鉤子處理。
第三步和第四步因非切換鉤子類型而註定失敗。第三步將失敗,因為相應的託管回調函數不會為該應用程序而設置。記住,這個DLL使用全局變數來跟蹤這些託管代理並且該鉤子DLL被載入到每一個進程空間。但是這個值僅在放鉤子的應用程序進程空間中設置。對於另外其它情況,它們全部為null。
Tim Sylvester在他的《Other hook types》一文中指出,使用一個共享內存區段將會解決這個問題。這是真實的,但是也如Tim所指出的,那些託管代理地址對於除了放鉤子的應用程序之外的任何進程是無意義的。這意味著,它們是無意義的並且不能在回調函數的執行過程中調用。那樣會有麻煩的。
因此,為了把這些回調函數使用於不執行上下文切換的鉤子類型,你需要某種進程間的通訊。
我已經試驗過這種思想-使用非託管的DLL鉤子回調函數中的進程外COM對象進行IPC。如果你能使這種方法工作,我將很高興了解到這點。至於我的嘗試,結果並不理想。基本原因是很難針對各種進程和它們的線程(CoInitialize(NULL))而正確地初始化COM單元。這是一個在你可以使用 COM對象之前的基本要求。
我不懷疑,一定有辦法來解決這個問題。但是我還沒有試用過它們,因為我認為它們僅有有限的用處。例如,CBT鉤子可以讓你取消一個窗口創建,如果你希望的話。可以想像,為使這能夠工作將會發生什麼。
1. 鉤子回調函數開始執行。
2. 調用非託管的鉤子DLL中的相應的鉤子回調函數。
3. 執行必須被路由回到主鉤子應用程序。
4. 該應用程序必須決定是否允許這一創建。
5. 調用必須被路由回仍舊在運行中的鉤子回調函數。
6. 在非託管的鉤子DLL中的鉤子回調函數從主鉤子應用程序接收到要採取的行動。
7. 在非託管的鉤子DLL中的鉤子回調函數針對CBT鉤子調用採取適當的行動。
8. 完成鉤子回調函數的執行。
這不是不可能的,但是不算好的。我希望這會消除在該庫中的圍繞被允許的和受限制的鉤子類型所帶來的神秘。
七、 其它
・庫文檔:我們已經包含了有關ManagedHooks類庫的比較完整的代碼文檔。當以"Documentation"構建配置進行編譯時,這被經由Visual Studio.NET轉換成標准幫助XML。最後,我們已使用NDoc來把它轉換成編譯的HTML幫助(CHM)。你可以看這個幫助文件,只需簡單地在該方案的解決方案資源管理器中點擊Hooks.chm文件或通過查找與該文相關的可下載的ZIP文件。
・增強的智能感知:如果你不熟悉Visual Studio.NET怎樣使用編譯的XML文件(pre-NDoc output)來為參考庫的工程增強智能感知,那麼讓我簡單地介紹一下。如果你決定在你的應用程序中使用這個類庫,你可以考慮復制該庫的一個穩定構建版本到你想參考它的位置。同時,還要把XML文檔文件 (SystemHooks\ManagedHooks\bin\Debug\Kennedy.ManagedHooks.xml)復制到相同的位置。當你添加一個參考到該庫時,Visual Studio.NET將自動地讀該文件並使用它來添加智能感知文檔。這是很有用的,特別是對於象這樣的第三方庫。
・單元測試:我相信,所有的庫都應有與之相應的單元測試。既然我是一家公司(主要負責針對.NET環境軟體的單元測試)的合夥人和軟體工程師,任何人不會對此感到驚訝。因而,你將會在名為ManagedHooksTests的解決方案中找到一個單元測試工程。為了運行該單元測試,你需要下載和安裝 HarnessIt-這個下載是我們的商業單元測試軟體的一個自由的試用版本。在該單元測試中,我對這給予了特殊的注意-在此處,方法的無效參數可能導致 C++內存異常的發生。盡管這個庫是相當簡單的,但該單元測試確實能夠幫助我在一些更為微妙的情況下發現一些錯誤。
・非託管的/託管的調試:有關混合解決方案(例如,本文的託管的和非託管的代碼)最為技巧的地方之一是調試問題。如果你想單步調試該C++代碼或在C++代碼中設置斷點,你必須啟動非託管的調試。這是一個Visual Studio.NET中的工程設置。注意,你可以非常順利地單步調試託管的和非託管的層,但是,在調試過程中,非託管的調試確實嚴重地減慢應用程序的裝載時間和執行速度。
八、 最後警告
很明顯,系統鉤子相當有力量;然而,使用這種力量應該是有責任性的。在系統鉤子出了問題時,它們不僅僅垮掉你的應用程序。它們可以垮掉在你的當前系統中運行的每個應用程序。但是到這種程度的可能性一般是很小的。盡管如此,在使用系統鉤子時,你還是需要再三檢查你的代碼。
我發現了一項可以用來開發應用程序的有用的技術-它使用系統鉤子來在微軟的虛擬PC上安裝你的喜愛的開發操作系統的一個拷貝和Visual Studio.NET。然後,你就可以在此虛擬的環境中開發你的應用程序。用這種方式,當你的鉤子應用程序出現錯誤時,它們將僅退出你的操作系統的虛擬實例而不是你的真正的操作系統。我已經不得不重啟動我的真正的OS-在這個虛擬OS由於一個鉤子錯誤崩潰時,但是這並不經常。
『叄』 您好!您能不能幫我寫一個調用鉤子函數的簡單C++程序
我有現成的,現在正在研究這個,已經實現了調用了,不過在滑鼠點擊的消息處理上還有一點問題,網上很多人做了很多工作,你自己研究下也可以實現,不難。
關鍵問題是應用程序對DLL調用時進出要注意,否則調試的時候容易出現死機的問題,特別是鉤子函數的處理,網上都是喜歡用一個顯示不同應用程序的滑鼠鉤子來實現,我搞了兩天才明白主要的問題所在,主要還是我自己理解不透,斷點不能亂設
需要交流聯系我,源代碼也可以給你
『肆』 關於c#全局鍵盤鉤子的問題。
throw的異常需要被catch
你沒有catch方法。
『伍』 如何通過svn提交到伺服器後,在伺服器端編譯C++項目 widnows伺服器
單獨執行post-commit可以運行,自動執行這個鉤子時報錯,因為沒有看到你的報錯信息,只能憑經驗判斷可能是path路徑問題,你可以嘗試在post-commit這個鉤子中調用powershell時,寫上powershell的完整路徑,然後看看效果如何。
如果不用powershell腳本的話,其實大部分操作也可以都放入post-commit中,你可以通過這個鉤子直接執行checkout指令和編譯器命令行,完成checkout和編譯的操作。post-commit.bat這個鉤子其實就是一個普通的批處理程序,只是會被SVN在commit成功時自動調用而已,所以你想實現什麼,就直接把指令寫到這個鉤子里就是了。
『陸』 會做Hook的進!不會的別打擾 !急!!!
鉤子的本質是一段用以處理系統消息的程序,通過系統調用,把它掛入系統。鉤子的種類很多,每種鉤子可以截獲並處理相應的消息,每當特定的消息發出,在到達目的窗口之前,鉤子程序先行截獲該消息、得到對此消息的控制權。此時鉤子函數可以對截獲的消息進行加工處理,甚至可以強制結束消息的傳遞。這有點類似與MFC中的PreTranslateMessage函數,所不同的是該函數只能用於攔截本進程中的消息,而對系統消息則無能為力。
二、Win32系統鉤子的實現
每種類型的鉤子均由系統來維護一個鉤子鏈,最近安裝的鉤子位於鏈的開始,擁有最高的優先順序,而最先安裝的鉤子則處在鏈的末尾。要實現Win32的系統鉤子,首先要調用SDK中的API函數SetWindowsHookEx來安裝這個鉤子函數,其原型是:
HHOOK SetWindowsHookEx(int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId);
其中,第一個參數是鉤子的類型,常用的有WH_MOUSE、WH_KEYBOARD、WH_GETMESSAGE等;第二個參數是鉤子函數的地址,當鉤子鉤到任何消息後便調用這個函數;第三個參數是鉤子函數所在模塊的句柄;第四個參數是鉤子相關函數的ID用以指定想讓鉤子去鉤哪個線程,為0時則攔截整個系統的消息此時為全局鉤子。如果指定確定的線程,即為線程專用鉤子。
全局鉤子函數必須包含在DLL(動態鏈接庫)中,而線程專用鉤子則可包含在可執行文件中。得到控制權的鉤子函數在處理完消息後,可以調用另外一個SDK中的API函數CallNextHookEx來繼續傳遞該消息。也可以通過直接返回TRUE來丟棄該消息,阻止該消息的傳遞。
使用全局鉤子函數時需要以DLL為載體,VC6中有三種形式的MFC DLL可供選擇,即Regular statically linked to MFC DLL(標准靜態鏈接MFC DLL)、Regular using the shared MFC DLL(標准動態鏈接MFC DLL)以及Extension MFC DLL(擴展MFC DLL)。第一種DLL在編譯時把使用的MFC代碼鏈接到DLL中,執行程序時不需要其他MFC動態鏈接類庫的支持,但體積較大;第二種DLL在運行時動態鏈接到MFC類庫,因而體積較小,但卻依賴於MFC動態鏈接類庫的支持;這兩種DLL均可被MFC程序和Win32程序使用。第三種DLL的也是動態連接,但做為MFC類庫的擴展,只能被MFC程序使用。
三、Win32 DLL
Win32 DLL的入口和出口函數都是DLLMain這同Win16 DLL是有區別的。只要有進程或線程載入和卸載DLL時,都會調用該函數,其原型是:
BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason, LPVOID lpvReserved);其中,第一個參數表示DLL的實例句柄;第三個參數系統保留;第二個參數指明了當前調用該動態連接庫的狀態,它有四個可能的值:DLL_PROCESS_ATTACH(進程載入)、DLL_THREAD_ATTACH(線程載入)、DLL_THREAD_DETACH(線程卸載)、DLL_PROCESS_DETACH(進程卸載)。在DLLMain函數中可以通過對傳遞進來的這個參數的值進行判別,根據不同的參數值對DLL進行必要的初始化或清理工作。由於在Win32環境下,所有進程的空間都是相互獨立的,這減少了應用程序間的相互影響,但大大增加了編程的難度。當進程在動態載入DLL時,系統自動把DLL地址映射到該進程的私有空間,而且也復制該DLL的全局數據的一份拷貝到該進程空間,每個進程所擁有的相同的DLL的全局數據其值卻並不一定是相同的。當DLL內存被映射到進程空間中,每個進程都有自己的全局內存拷貝,載入DLL的每一個新的進程都重新初始化這一內存區域,也就是說進程不能再共享DLL。因此,在Win32環境下要想在多個進程中共享數據,就必須進行必要的設置。一種方法便是把這些需要共享的數據單獨分離出來,放置在一個獨立的數據段里,並把該段的屬性設置為共享,建立一個內存共享的DLL。
四、全局共享數據的實現
可以用#pragma data_seg建立一個新的數據段並定義共享數據,其具體格式為:
#pragma data_seg ("shareddata")
HWND sharedwnd=NULL;//共享數據
#pragma data_seg()
所有在data_seg pragmas語句之間聲明的變數都將在shareddata段中。僅定義一個數據段還不能達到共享數據的目的,還要告訴編譯器該段的屬性,有兩種方法可以實現該目的(其效果是相同的),一種方法是在.DEF文件中加入如下語句:
SETCTIONS
shareddata READ WRITE SHARED
另一種方法是在項目設置鏈接選項中加入如下語句:
/SECTION:shareddata,rws
五、滑鼠鉤子程序示例
本示常式序用到全局鉤子函數,程序分兩部分:可執行程序MouseDemo和動態連接庫MouseHook。首先編制MFC擴展動態連接庫MouseHook.dll:
(一)選擇MFC AppWizard(DLL)創建項目Mousehook;
(二)選擇MFC Extension DLL(MFC擴展DLL)類型;
(三)通過Project菜單的AddToProject子菜單的"New…"添加頭文件MouseHook.h。
(四)在頭文件中建立鉤子類:
class AFX_EXT_CLASS CMouseHook:public CObject
{
public:
CMouseHook(); //鉤子類的構造函數
~CMouseHook(); //鉤子類的析構函數
BOOL StartHook(HWND hWnd); //安裝鉤子函數
BOOL StopHook(); //卸載鉤子函數
};
(五)在MouseHook.cpp文件中加入#include"MouseHook.h"語句;
(六)加入全局共享數據變數:
#pragma data_seg("mydata")
HWND glhPrevTarWnd=NULL; //上次滑鼠所指的窗口句柄
HWND glhDisplayWnd=NULL; //顯示目標窗口標題編輯框的句柄
HHOOK glhHook=NULL; //安裝的滑鼠勾子句柄
HINSTANCE glhInstance=NULL; //DLL實例句柄
#pragma data_seg()
(七)在DEF文件中定義段屬性:
SECTIONS
mydata READ WRITE SHARED
(八)在主文件MouseHook.cpp的DllMain函數中加入保存DLL實例句柄的語句:
extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
UNREFERENCED_PARAMETER(lpReserved);
if (dwReason == DLL_PROCESS_ATTACH)
{
if (!AfxInitExtensionMole(MouseHookDLL, hInstance))
return 0;
new CDynLinkLibrary(MouseHookDLL);
glhInstance=hInstance; //插入保存DLL實例句柄
}
else if (dwReason == DLL_PROCESS_DETACH)
{
AfxTermExtensionMole(MouseHookDLL);
}
return 1; // ok
}
這個函數最重要的部分是調用AfxInitExtensionMole(),它初始化DLL使它在MFC框架中正確的工作。它需要傳遞給DllMain()的DLL實例句柄和AFX_EXTENSION_MODULE結構,結構中存在著對MFC有用的信息。
(九) 類CMouseHook的成員函數的具體實現:
Cmousehook::Cmousehook() //類構造函數
{
}
Cmousehook::~Cmousehook() //類析構函數
{
stophook();
}
BOOL Cmousehook::starthook(HWND hWnd) //安裝鉤子並設定接收顯示窗口句柄
{
BOOL bResult=FALSE;
glhHook=SetWindowsHookEx(WH_MOUSE,MouseProc,glhInstance,0);
if(glhHook!=NULL)
bResult=TRUE;
glhDisplayWnd=hWnd; //設置顯示目標窗口標題編輯框的句柄
return bResult;
}
BOOL Cmousehook::stophook() //卸載鉤子
{
BOOL bResult=FALSE;
if(glhHook)
{
bResult= UnhookWindowsHookEx(glhHook);
if(bResult)
{
glhPrevTarWnd=NULL;
glhDisplayWnd=NULL;//清變數
glhHook=NULL;
}
}
return bResult;
}
(十) 鉤子函數的實現
LRESULT WINAPI MouseProc(int nCode,WPARAM wparam,LPARAM lparam)
{
LPMOUSEHOOKSTRUCT pMouseHook=(MOUSEHOOKSTRUCT FAR *) lparam;
if (nCode>=0)
{
HWND glhTargetWnd=pMouseHook->hwnd; //取目標窗口句柄
HWND ParentWnd=glhTargetWnd;
while (ParentWnd !=NULL)
{
glhTargetWnd=ParentWnd;
ParentWnd=GetParent(glhTargetWnd); //取應用程序主窗口句柄
}
if(glhTargetWnd!=glhPrevTarWnd)
{
char szCaption[100];
GetWindowText(glhTargetWnd,szCaption,100); //取目標窗口標題
if(IsWindow(glhDisplayWnd))
SendMessage(glhDisplayWnd,WM_SETTEXT,0,(LPARAM)(LPCTSTR)szCaption);
glhPrevTarWnd=glhTargetWnd; //保存目標窗口
}
}
return CallNextHookEx(glhHook,nCode,wparam,lparam); //繼續傳遞消息
}
編譯完成便可得到運行時所需的滑鼠鉤子的動態連接庫MouseHook.dll和鏈接時用到的MouseHook.lib。
六、集成
下面新建一調用滑鼠鉤子動態連接庫的鉤子可執行程序:
(一) 用MFC的AppWizard(EXE)創建項目MouseDemo;
(二) 選擇"基於對話應用",其餘幾步均為確省;
(三) 在對話框上加入一個編輯框IDC_EDIT1;
(四) 在MouseDemo.h中加入對Mousehook.h的包含語句:#Include"Mousehook.h";
(五) 在CMouseDemoDlg.h的CMouseDemoDlg類定義中添加私有數據成員:CMouseHook m_hook;
(六) 在OnInitDialog函數的"TODO注釋"後添加:
CWnd * pwnd=GetDlgItem(IDC_EDIT1); //取得編輯框的類指針
m_hook.StartHook(pwnd->GetSafeHwnd()); //取得編輯框的窗口句柄並安裝鉤子
(七)鏈接DLL庫,即把Mousehook.lib加入到項目設置鏈接標簽中;
(八)把MouseHook.h和MouseHook.lib復制到MouseDemo工程目錄中,MouseHook.dll復制到Debug目錄下。編譯執行程序即可。當滑鼠滑過窗口時便會在編輯框中將此窗口的標題顯示出來。
結論:
系統鉤子具有相當強大的功能,通過這種技術可以對幾乎所有的Windows
系統消息進行攔截、監視、處理。這種技術可以廣泛應用於各種軟體,尤其是需要
有監控、自動記錄等對系統進行監測功能的軟體。本程序只對滑鼠消息進行攔截,
相應的也可以在Win32環境下對鍵盤、埠等應用此技術完成特定的功能。
『柒』 魔獸地圖編輯器YDWE的鉤子bug太多了。我想用程序自己寫鉤子。
很多人都以為未排泄是大問題,其實不是,一個觸發沒有排泄是不會導致死機的,他只會在運行幾千次以後造成游戲卡頓。而直接死機的原因大部分是因為觸發沒寫好不斷循環造成。
你這個觸發本身沒有問題,看看是不是鉤子導致了其他觸發不停循環,基本可以肯定問題出在其他觸發,而不是這里。
『捌』 C++日誌鉤子的用法。鉤子過程的編寫。高手來幫忙
text=u_key;
int 是無法隱式轉換為 CString 的,應該用
text.Format(_T("%d"), u_key);
另外樓主只說編譯出來的程序有問題,沒有說問題在哪。
『玖』 vb中SetWindowsHookEx詳細用法及舉例
基本概念
鉤子(Hook),是Windows消息處理機制的一個平台,應用程序可以在上面設置子程以監視指定窗口的某種消息,而且所監視的窗口可以是其他進程所創建的。當消息到達後,在目標窗口處理函數之前處理它。鉤子機制允許應用程序截獲處理window消息或特定事件。
鉤子實際上是一個處理消息的程序段,通過系統調用,把它掛入系統。每當特定的消息發出,在沒有到達目的窗口前,鉤子程序就先捕獲該消息,亦即鉤子函數先得到控
制權。這時鉤子函數即可以加工處理(改變)該消息,也可以不作處理而繼續傳遞該消息,還可以強制結束消息的傳遞。
--------------------------------------------------------------------------------
運行機制
1、鉤子鏈表和鉤子子程:
每一個Hook都有一個與之相關聯的指針列表,稱之為鉤子鏈表,由系統來維護。這個列表的指針指向指定的,應用程 序定義的,被Hook子程調用的回調函數,也就是該鉤子的各個處理子程。當與指定的Hook類型關聯的消息發生時,系統就把這個消息傳遞到Hook子程。 一些Hook子程可以只監視消息,或者修改消息,或者停止消息的前進,避免這些消息傳遞到下一個Hook子程或者目的窗口。最近安裝的鉤子放在鏈的開始, 而最早安裝的鉤子放在最後,也就是後加入的先獲得控制權。
Windows 並不要求鉤子子程的卸載順序一定得和安裝順序相反。每當有一個鉤子被卸載,Windows 便釋放其佔用的內存,並更新整個Hook鏈表。如果程序安裝了鉤子,但是在尚未卸載鉤子之前就結束了,那麼系統會自動為它做卸載鉤子的操作。
鉤子子程是一個應用程序定義的回調函數(CALLBACK Function),不能定義成某個類的成員函數,只能定義為普通的C函數。用以監視系統或某一特定類型的事件,這些事件可以是與某一特定線程關聯的,也可以是系統中所有線程的事件。
鉤子子程必須按照以下的語法:
LRESULT CALLBACK HookProc
(
int nCode,
WPARAM wParam,
LPARAM lParam
);
HookProc是應用程序定義的名字。
nCode參數是Hook代碼,Hook子程使用這個參數來確定任務。這個參數的值依賴於Hook類型,每一種Hook都有自己的Hook代碼特徵字元集。
wParam和lParam參數的值依賴於Hook代碼,但是它們的典型值是包含了關於發送或者接收消息的信息。
2、鉤子的安裝與釋放:
使用API函數SetWindowsHookEx()把一個應用程序定義的鉤子子程安裝到鉤子鏈表中。 SetWindowsHookEx函數總是在Hook鏈的開頭安裝Hook子程。當指定類型的Hook監視的事件發生時,系統就調用與這個Hook關聯的 Hook鏈的開頭的Hook子程。每一個Hook鏈中的Hook子程都決定是否把這個事件傳遞到下一個Hook子程。Hook子程傳遞事件到下一個 Hook子程需要調用CallNextHookEx函數。
HHOOK SetWindowsHookEx(
int idHook, // 鉤子的類型,即它處理的消息類型
HOOKPROC lpfn, // 鉤子子程的地址指針。如果dwThreadId參數為0
// 或是一個由別的進程創建的線程的標識,
// lpfn必須指向DLL中的鉤子子程。
// 除此以外,lpfn可以指向當前進程的一段鉤子子程代碼。
// 鉤子函數的入口地址,當鉤子鉤到任何消息後便調用這個函數。
HINSTANCE hMod, // 應用程序實例的句柄。標識包含lpfn所指的子程的
DLL。
// 如果dwThreadId 標識當前進程創建的一個線程,
// 而且子程代碼位於當前進程,hMod必須為NULL。
// 可以很簡單的設定其為本應用程序的實例句柄。
DWORD dwThreadId // 與安裝的鉤子子程相關聯的線程的標識符。
// 如果為0,鉤子子程與所有的線程關聯,即為全局鉤子。
);
函數成功則返回鉤子子程的句柄,失敗返回NULL。
以上所說的鉤子子程與線程相關聯是指在一鉤子鏈表中發給該線程的消息同時發送給鉤子子程,且被鉤子子程先處理。
在鉤子子程中調用得到控制權的鉤子函數在完成對消息的處理後,如果想要該消息繼續傳遞,那麼它必須調用另外一個 SDK中的API函數CallNextHookEx來傳遞它,以執行鉤子鏈表所指的下一個鉤子子程。這個函數成功時返回鉤子鏈中下一個鉤子過程的返回值, 返回值的類型依賴於鉤子的類型。這個函數的原型如下:
LRESULT CallNextHookEx
(
HHOOK hhk;
int nCode;
WPARAM wParam;
LPARAM lParam;
);
hhk為當前鉤子的句柄,由SetWindowsHookEx()函數返回。
NCode為傳給鉤子過程的事件代碼。
wParam和lParam 分別是傳給鉤子子程的wParam值,其具體含義與鉤子類型有關。
鉤子函數也可以通過直接返回TRUE來丟棄該消息,並阻止該消息的傳遞。否則的話,其他安裝了鉤子的應用程序將不會接收到鉤子的通知而且還有可能產生不正確的結果。
鉤子在使用完之後需要用UnHookWindowsHookEx()卸載,否則會造成麻煩。釋放鉤子比較簡單,UnHookWindowsHookEx()只有一個參數。函數原型如下:
UnHookWindowsHookEx
(
HHOOK hhk;
);
函數成功返回TRUE,否則返回FALSE。
3、一些運行機制:
在Win16環境中,DLL的全局數據對每個載入它的進程來說都是相同的;而在Win32環境中,情況卻發生了變化,DLL函數中的代碼所創建的任何對象(包括變數)都歸調用它的線程或進程所有。當進程在載入DLL時,操作系統自動把DLL地址映射到該進程的私有空間,也就是進程的虛擬地址空間,而且也復制該DLL的全局數據的一份拷貝到該進程空間。也就是說每個進程所擁有的相同的DLL的全局數據,它們的名稱相同,但其值卻並不一定是相同的,而且是互不幹涉的。
因此,在Win32環境下要想在多個進程中共享數據,就必須進行必要的設置。在訪問同一個Dll的各進程 之間共享存儲器是通過存儲器映射文件技術實現的。也可以把這些需要共享的數據分離出來,放置在一個獨立的數據段里,並把該段的屬性設置為共享。必須給這些 變數賦初值,否則編譯器會把沒有賦初始值的變數放在一個叫未被初始化的數據段中。
#pragma data_seg預處理指令用於設置共享數據段。例如:
#pragma data_seg("SharedDataName")
HHOOK hHook=NULL;
#pragma data_seg()
在#pragma data_seg("SharedDataName")和#pragma data_seg()之間的所有變數將被訪問該Dll的所有進程看到和共享。再加上一條指令#pragma comment(linker,"/section:.SharedDataName,rws"),那麼這個數據節中的數據可以在所有DLL的實例之間共 享。所有對這些數據的操作都針對同一個實例的,而不是在每個進程的地址空間中都有一份。
當進程隱式或顯式調用一個動態庫里的函數時,系統都要把這個動態庫映射到這個進程的虛擬地址空間里(以下簡稱"地址空間")。這使得DLL成為進程的一部分,以這個進程的身份執行,使用這個進程的堆棧。
4、系統鉤子與線程鉤子:
SetWindowsHookEx()函數的最後一個參數決定了此鉤子是系統鉤子還是線程鉤子。
線程勾子用於監視指定線程的事件消息。線程勾子一般在當前線程或者當前線程派生的線程內。
系統勾子監視系統中的所有線程的事件消息。因為系統勾子會影響系統中所有的應用程序,所以勾子函數必須放在獨立的動態鏈接庫(DLL) 中。系統自動將包含"鉤子回調函數"的DLL映射到受鉤子函數影響的所有進程的地址空間中,即將這個DLL注入了那些進程。
幾點說明:
(1)如果對於同一事件(如滑鼠消息)既安裝了線程勾子又安裝了系統勾子,那麼系統會自動先調用線程勾子,然後調用系統勾子。
(2)對同一事件消息可安裝多個勾子處理過程,這些勾子處理過程形成了勾子鏈。當前勾子處理結束後應把勾子信息傳遞給下一個勾子函數。
(3)勾子特別是系統勾子會消耗消息處理時間,降低系統性能。只有在必要的時候才安裝勾子,在使用完畢後要及時卸載。
--------------------------------------------------------------------------------
鉤子類型
每一種類型的Hook可以使應用程序能夠監視不同類型的系統消息處理機制。下面描述所有可以利用的Hook類型。
1、WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks
WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks使你可以監視發送到窗口過程的消息。系統在消息發送到接收窗口過程之前調用WH_CALLWNDPROC Hook子程,並且在窗口過程處理完消息之後調用WH_CALLWNDPROCRET Hook子程。
WH_CALLWNDPROCRET Hook傳遞指針到CWPRETSTRUCT結構,再傳遞到Hook子程。
CWPRETSTRUCT結構包含了來自處理消息的窗口過程的返回值,同樣也包括了與這個消息關聯的消息參數。
2、WH_CBT Hook
在以下事件之前,系統都會調用WH_CBT Hook子程,這些事件包括:
1. 激活,建立,銷毀,最小化,最大化,移動,改變尺寸等窗口事件;
2. 完成系統指令;
3. 來自系統消息隊列中的移動滑鼠,鍵盤事件;
4. 設置輸入焦點事件;
5. 同步系統消息隊列事件。
Hook子程的返回值確定系統是否允許或者防止這些操作中的一個。
3、WH_DEBUG Hook
在系統調用系統中與其他Hook關聯的Hook子程之前,系統會調用WH_DEBUG Hook子程。你可以使用這個Hook來決定是否允許系統調用與其他Hook關聯的Hook子程。
4、WH_FOREGROUNDIDLE Hook
當應用程序的前台線程處於空閑狀態時,可以使用WH_FOREGROUNDIDLE Hook執行低優先順序的任務。當應用程序的前台線程大概要變成空閑狀態時,系統就會調用WH_FOREGROUNDIDLE Hook子程。
5、WH_GETMESSAGE Hook
應用程序使用WH_GETMESSAGE Hook來監視從GetMessage or PeekMessage函數返回的消息。你可以使用WH_GETMESSAGE Hook去監視滑鼠和鍵盤輸入,以及其他發送到消息隊列中的消息。
6、WH_JOURNALPLAYBACK Hook
WH_JOURNALPLAYBACK Hook使應用程序可以插入消息到系統消息隊列。可以使用這個Hook回放通過使用WH_JOURNALRECORD Hook記錄下來的連續的滑鼠和鍵盤事件。只要WH_JOURNALPLAYBACK Hook已經安裝,正常的滑鼠和鍵盤事件就是無效的。
WH_JOURNALPLAYBACK Hook是全局Hook,它不能象線程特定Hook一樣使用。
WH_JOURNALPLAYBACK Hook返回超時值,這個值告訴系統在處理來自回放Hook當前消息之前需要等待多長時間(毫秒)。這就使Hook可以控制實時事件的回放。
WH_JOURNALPLAYBACK是system-wide local hooks,它們不會被注射到任何行程位址空間。
7、WH_JOURNALRECORD Hook
WH_JOURNALRECORD Hook用來監視和記錄輸入事件。典型的,可以使用這個Hook記錄連續的滑鼠和鍵盤事件,然後通過使用WH_JOURNALPLAYBACK Hook來回放。
WH_JOURNALRECORD Hook是全局Hook,它不能象線程特定Hook一樣使用。
WH_JOURNALRECORD是system-wide local hooks,它們不會被注射到任何行程位址空間。
8、WH_KEYBOARD Hook
在應用程序中,WH_KEYBOARD Hook用來監視WM_KEYDOWN and WM_KEYUP消息,這些消息通過GetMessage or PeekMessage function返回。可以使用這個Hook來監視輸入到消息隊列中的鍵盤消息。
9、WH_KEYBOARD_LL Hook
WH_KEYBOARD_LL Hook監視輸入到線程消息隊列中的鍵盤消息。
10、WH_MOUSE Hook
WH_MOUSE Hook監視從GetMessage 或者 PeekMessage 函數返回的滑鼠消息。使用這個Hook監視輸入到消息隊列中的滑鼠消息。
11、WH_MOUSE_LL Hook
WH_MOUSE_LL Hook監視輸入到線程消息隊列中的滑鼠消息。
12、WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks
WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我們可以監視菜單,滾動條,消息框,對話框消息並且發現用戶使用ALT+TAB or ALT+ESC 組合鍵切換窗口。WH_MSGFILTER Hook只能監視傳遞到菜單,滾動條,消息框的消息,以及傳遞到通過安裝了Hook子程的應用程序建立的對話框的消息。WH_SYSMSGFILTER Hook監視所有應用程序消息。
WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我們可以在模式循環期間過濾消息,這等價於在主消息循環中過濾消息。
通過調用CallMsgFilter function可以直接的調用WH_MSGFILTER Hook。通過使用這個函數,應用程序能夠在模式循環期間使用相同的代碼去過濾消息,如同在主消息循環里一樣。
13、WH_SHELL Hook
外殼應用程序可以使用WH_SHELL Hook去接收重要的通知。當外殼應用程序是激活的並且當頂層窗口建立或者銷毀時,系統調用WH_SHELL Hook子程。
WH_SHELL 共有5鍾情況:
1. 只要有個top-level、unowned 窗口被產生、起作用、或是被摧毀;
2. 當Taskbar需要重畫某個按鈕;
3. 當系統需要顯示關於Taskbar的一個程序的最小化形式;
4. 當目前的鍵盤布局狀態改變;
5. 當使用者按Ctrl+Esc去執行Task Manager(或相同級別的程序)。
按照慣例,外殼應用程序都不接收WH_SHELL消息。所以,在應用程序能夠接收WH_SHELL消息之前,應用程序必須調用SystemParametersInfo function注冊它自己。
使用原型:
SetWindowsHookEx(WH_KEYBOARD, KeyBoardProc, HInstance, 0);
『拾』 鍵盤鉤子怎麼 使用
I:設置鉤子
設置鉤子是通過SetWindowsHookEx ()的API函數.
原形: HHOOK SetWindowsHookEx(int idHook,HOOKPROC lpfn,HINSTANCE hMod,DWORD dwThreadId)
idhook:裝入鉤子的類型.
lpfn: 鉤子進程的入口地址
hMod: 應用程序的事件句柄
dwThreadId: 裝入鉤子的線程標示
參數:
idHook:
這個參數可以是以下值:
WH_CALLWNDPROC、WH_CALLWNDPROCRET、WH_CBT、WH_DEBUG、WH_FOREGROUNDIDLE、WH_GETMESSAGE、WH_JOURNALPLAYBACK、WH_JOURNALRECORD、WH_KEYBOARD、WH_KEYBOARD_LL、WH_MOUSE、WH_MOUSE_LL、WH_MSGFILTER、WH_SHELL、WH_SYSMSGFILTER。
對於這些參數,我不想一一加以解釋,因為MSDN中有關於他們的詳細註解。我只挑選其中的幾個加以中文說明。
WH_KEYBOARD:一旦有鍵盤敲打消息(鍵盤的按下、鍵盤的彈起),在這個消息被放在應用程序的消息隊列前,WINDOWS將會調用你的鉤子函數。鉤子函數可以改變和丟棄鍵盤敲打消息。
WH_MOUSE:每個滑鼠消息在被放在應用程序的消息隊列前,WINDOWS將會調用你的鉤子函數。鉤子函數可以改變和丟棄滑鼠消息。
WH_GETMESSAGE:每次當你的應用程序調用一個GetMessage()或者一個PeekMessage()為了去從應用程序的消息隊列中要求一個消息時,WINDOWS都會調用你的鉤子函數。而鉤子函數可以改變和丟棄這個消息。
II:釋放鉤子
鉤子的釋放使用的是UnhookWindowsHookEx()函數
原形:BOOL UnhookWindowsHookEx( HHOOK hhk )
UnhookWindowsHookEx()函數將釋放的是鉤子鏈中函數SetWindowsHookEx所裝入的鉤子進程。
hhk: 將要釋放的鉤子進程的句柄。
III:鉤子進程
鉤子進程使用函數HookProc;其實HookProc僅僅只是應用程序定義的符號。比如你可以寫成KeyBoardHook.但是參數是不變的。Win32 API提供了諸如:CallWndProc、GetMsgProc、DebugProc、CBTProc、MouseProc、KeyboardProc、MessageProc等函數,對於他們的詳細講解,可以看MSDN我在此只講解一下KeyBoardHook的含義。
原形:LRESULT CALLBACK KeyBoardHook (int nCode, WPARAM wParam, LPARAM lParam)
說明:鉤子進程是一些依附在一個鉤子上的一些函數,因此鉤子進程只被WINDOWS調用而不被應用程序調用,他們有時就需要作為一個回調函數(CALLBACK)。
參數說明:
nCode:鉤子代碼,鉤子進程使用鉤子代碼去決定是否執行。而鉤子代碼的值是依靠鉤子的種類來定的。每種鉤子種類都有他們自己一系列特性的代碼。比如對於WH_KEYBOARD,鉤子代碼的參數有:HC_ACTION,HC_NOREMOVE。HC_ACTION的意義:參數wParam 和lParam 包含了鍵盤敲打消息的信息,HC_NOREMOVE的意義:參數wParam 和lParam包含了鍵盤敲打消息的信息,並且,鍵盤敲打消息一直沒有從消息隊列中刪除。(應用程序調用PeekMessage函數,並且設置PM_NOREMOVE標志)。也就是說當nCode等於HC_ACTION時,鉤子進程必須處理消息。而為HC_NOREMOVE時,鉤子進程必須傳遞消息給CallNextHookEx函數,而不能做進一步的處理,而且必須有CallNextHookEx函數的返回值。
wParam:鍵盤敲打所產生的鍵盤消息,鍵盤按鍵的虛擬代碼。
lParam:包含了消息細節。
注意:如果鉤子進程中nCode小於零,鉤子進程必須返回(return) CallNextHookEx(nCode,wParam,lParam);而鉤子進程中的nCode大於零,但是鉤子進程並不處理消息,作者推薦你調用CallNextHookEx並且返回該函數的返回值。否則,如果另一個應用程序也裝入WH_KEYBOARD 鉤子,那麼該鉤子將不接受鉤子通知並且返回一個不正確的值。如果鉤子進程處理了消息,它可能返回一個非零值去阻止系統傳遞該信息到其它剩下的鉤子或者windows進程。所以最好在鉤子進程的最後都返回CallNextHookEx的返回值。
IV:調用下一個鉤子函數
調用下一個鉤子函數時使用CallNexHookEx函數。
原形:LRESULT CallNextHookEx( HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam )
CallNexHookEx()函數用於對當前鉤子鏈中的下一個鉤子進程傳遞鉤子信息,一個鉤子進程既可以在鉤子信息處理前,也可以在鉤子信息處理後調用該函數。為什麼使用該函數已在iii鉤子進程中的「注意」中,加以了詳細的說明。
hhk: 當前鉤子的句柄
nCode: 傳送到鉤子進程的鉤子代碼。
wParam:傳送到鉤子進程的值。
lParam:傳送到鉤子進程的值。
參數:
hhk: 當前鉤子的句柄. 應用程序接受這個句柄,作為先前調用SetWindowsHookE函數的結果
nCode: 傳送到鉤子進程的鉤子代碼,下一個鉤子進程使用這個代碼以此決定如何處理鉤子信息
wParam:傳送給鉤子進程的wParam 參數值 ,參數值的具體含義與當前鉤子鏈的掛接的鉤子類型有關
lParam : 傳送給鉤子進程的wParam 參數值 ,參數值的具體含義與當前鉤子鏈的掛接的鉤子類型有關
返回值:返回值是鏈中下一個鉤子進程返回的值,當前鉤子進程必須返回這個值,返回值的具體含義與掛接的鉤子類型有關,詳細信息請參看具體的鉤子進程描述。
V 建立一個動態連接庫(DLL)
當我們熟悉了以上的各個函數後,現在我們開始編寫一個動態連接庫(DLL)。在這兒我採用的是WIN32 DLL,而不是MFC DLL。而且以下所有的程序也都是採用C語言去編寫。這主要是因為使用WIN32 API能夠更詳細、更全面的控製程序的如何執行,而使用MFC,一些低級的控制是不可能實現的(當然,僅對該程序來說,也是可以使用MFC的)。
1:建立一個動態連接庫的.cpp文件。比如我們現在建立一個名為hookdll.cpp的文件。在hookdll.cpp的文件中加上如下內容:
#include <windows.h>
#include "string.h"
#include "stdio.h"
HINSTANCE hInst;
#pragma data_seg("hookdata")
HHOOK oldkeyhook=0;
#pragma data_seg()
#pragma comment(linker,"/SECTION:hookdata,RWS")
#define DllExport extern "C"__declspec(dllexport)
DllExport LRESULT CALLBACK KeyBoardProc(int nCode,WPARAM wParam, LPARAM lParam );
DllExport void InstallHook(int nCode);
DllExport void EndHook(void);
BOOL WINAPI DllMain(HINSTANCE hInstance,ULONG What,LPVOID NotUsed)
{
switch(What)
{
case DLL_PROCESS_ATTACH:
hInst = hInstance;
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return 1;
}
void InstallHook(int nCode)
{
oldkeyhook = SetWindowsHookEx(WH_KEYBOARD,(HOOKPROC)KeyBoardProc,hInst,0);
}
DllExport LRESULT CALLBACK KeyBoardProc(int nCode,WPARAM wParam, LPARAM lParam )
{
WPARAM j;
FILE *fp;
if(lParam&0x80000000)
{
j = wParam;
fp=fopen("c:\hook\key.txt","a");
fprintf(fp,"%4d",j);
fclose(fp);
}
return CallNextHookEx(oldkeyhook,nCode,wParam,lParam);
}
void EndHook(void)
{
UnhookWindowsHookEx(oldkeyhook);
}
這個動態連接庫的源代碼hookdll.cpp包含了鍵盤處理函數,設置鉤子,退出鉤子函數。並將鍵盤敲下的鍵以值的格式存入到c:hookkey.txt文件中。以下是對該文件的詳細的解釋。
使用包含在DLL的函數,必須將其導入。導入操作時通過dllimport來完成的,dllexport和dllimport都是vc(visual C++)和bc(Borland C++)所支持的擴展的關鍵字。但是dllexport和dllimport關鍵字不能被自身所使用,因此它的前面必須有另一個擴展關鍵字__declspec。通用格式如下:__declspec(specifier)其中specifier是存儲類標示符。對於DLL,specifier將是dllexport和dllimport。而且為了簡化說明導入和導出函數的語句,用一個宏名來代替__declspec.在此程序中,使用的是DllExport。如果用戶的DLL被編譯成一個C++程序,而且希望C程序也能使用它,就需要增加「C」的連接說明。#define DllExport extern "C"__declspec(dllexport),這樣就避免了標准C++命名損壞。(當然,如果讀者正在編譯的是C程序,就不要加入extern 「C」,因為不需要它,而且編譯器也不接受它)。有了宏定義,現在就可以用一個簡單的語句就可以導出函數了,比如:
DllExport LRESULT CALLBACK KeyBoardProc(int nCode,WPARAM wParam, LPARAM lParam );DllExport void InstallHook(int nCode);DllExport void EndHook(void);
第一個#pragma 語句創造數據段,這里命名為hookdata。其實也可以命名為您喜歡的任意的一個名稱。#pragma 語句之後的所有初始化的變數都進入hookdata段中。第二個#pragma語句是數據段的結束標志。對變數進行專門的初始化是很重要的,否則編譯程序將把它們放在普通的未初始化的段中而不是放在hookdata中。
但是鏈接程序必須直到有一個hookdata段。我們可以在Project Setting(vc6.0) 對話框中選擇Link選項,選中HOOKDLL時在Project Options域(在Release 和Debug配置中均可),包含下面的連接語句:/SECTION:hookdata,RWS字母RWS是表明該段具有讀、寫、和共享屬性。當然,您也可以直接用DLL源代碼指定鏈接程序就像HOOKDLL.c那樣:#pragma comment(linker,"/SECTION:hookdata,RWS")。
由於有些DLL需要特殊的啟動和終止代碼。為此,所有的DLL都有一個名為DllMain()的函數,當初始化或終止DLL時調用該函數。一般在動態連結庫的資源文件中定義此函數。不過如果沒有定義它,則編譯器會自動提供預設的形式。
原型為:BOOL WINAPI DllMain(HINSTANCE hInstance,ULONG What,LPVOID NotUsed)
參數:
hInstance:DLL實例句柄
What:指定所發生的操作
NotUsed:保留參數
其中What的值可以為以下值:
DLL_PROCESS_ATTACH:進程開始使用DLL
DLL_PROCESS_DETACH:進程正在釋放DLL
DLL_THREAD_ATTACH:進程已創建一個新的線程
DLL_THREAD_DETACH:進程已舍棄了一個線程
總的來說,無論何時調用DllMain()函數,都必須根據What的內容來採取適當的動作。這種適當的動作可以什麼都不做,但不是返回非零值。
DllMain()接下來的便是設置鉤子,鍵盤處理,和釋放鉤子。
2:建立頭文件
正如應用程序所使用的其它任何庫函數一樣,程序也必須包含dll內的函數的原型。所有得Windows程序都必須包含windows.h的原因。所以我們現在建立一個頭文件hookdll.h如下:
#define DllImport extern"C"__declspec(dllimport)
DllImport void InstallHook(int nCode);
DllImport LRESULT CALLBACK KeyBoardProc (int nCode,WPARAM wParam, LPARAM lParam );
DllImport void EndHook(void);
使用dllimport主要是為了使代碼更高效,因此推薦使用它。但是在導入數據時是需要dllimport的。當完成了上面的程序後,建一個項目工程,不妨為hookdll,然後將hookdll.c插入導項目工程中,編譯,則可以生成了hookdll.dll和hookdll.lib。
3:建立程序主文件
我們在上面作的所有得工作都是為現在的主程序打得基礎。其實當我們完成了Dll文件後,剩下的就是調用設置鉤子函數:InstallHook 。如果你對windows編程十分的熟悉,那麼你可以在你任何需要的時候來調用InstallHook。但是在你必須記住在你退出程序的時候你需要調EndHook以便釋放你所裝入的鉤子函數。現在我在建立了一個hookspy.cpp,並將生成好的hookdll.dll和hookdll.lib拷貝到從一個目錄下,並建立一個hookspy的項目工程。將hookspy.cpp,hookdll.dll,hookdll.lib,hookdll.h插入到項目工程中去。然後在建立windows窗口時就將鉤子設置,在退出程序時退出鉤子函數。比如:
case WM_CREATE:
InstallHook(TRUE);
break;
case WM_DESTROY: //terminate the program
EndHook();
PostQuitMessage(0);
break;