如何寫一個管理全域性鉤子視窗建立和銷燬
介紹 在幾乎所有程式設計師的職業生涯中,都會遇到以下問題: 他的應用程式需要某種閃亮的特性,需要他在開啟或關閉新視窗時得到通知。 在c++的時代,這是相當簡單和直接的。只需將你的應用程式註冊為WM_DESTROY和WM_CREATE的鉤子。 不幸的是,隨著。net和託管應用程式的引入,這種情況發生了改變,實現起來更加困難。 在本文中,我們將討論一個實現,該實現允許您註冊事件,當建立新視窗或舊視窗被銷燬時,這些事件將向您發出訊號。 背景 我目前正在寫一個應用程式,在其他視窗上執行操作,並正在尋找一個簡單的方法,以獲得回撥每當一個新視窗被建立。我用c#來做這個。 經過幾天的internet搜尋,我意識到。net中有一種方法可以得到這樣的回撥。 在Win32 API中有一種方法是通過呼叫方法SetWindowsHookEx。然而,要讓它在所有的windows(不僅僅是在你的AppDomain中建立的windows)中工作,你需要註冊一個所謂的“全域性鉤子”,這在. net中是不可能的。要使全域性鉤子工作,需要將DLL注入到其他程序中。這在託管dll中是不可能的! 因此,我提出了一個與SetWindowsHookEx幾乎相同的解決方案,但僅限於在建立或銷燬視窗時傳送事件。 有關更多背景資訊,請參見此連結。 使用的程式碼 我的解決方案包含在一個檔案中,由兩個類組成: 隱藏,複製Code
public class WindowHookEventArgs { public IntPtr Handle = IntPtr.Zero; public string WindowTitle = null; public string WindowClass = null; public override string ToString() { return "[WindowHookEventArgs|Title:" + WindowTitle + "|Class:" + WindowClass + "|Handle:" + Handle + "]"; } }
這個類只包含有關所引發事件的資訊,併為您提供視窗控制代碼(在c++中通常稱為hWnd)。 另一個類是WindowHookNet,它是一個單例。我選擇將它實現為一個單例,以避免當這個類的十二個或更多例項持續拉取所有開啟的視窗時出現效能問題。 首先,你有兩個委託: 隱藏,複製Code
public delegate void WindowHookDelegate(object aSender, WindowHookEventArgs aArgs); private delegate bool EnumDelegate(IntPtrhWnd, int lParam);
WindowHookDelegate用於註冊由WindowHookNet引發的事件。EnumDelegate在內部用於檢索當前開啟的所有視窗的列表。 接下來是四個事件。兩個私人和兩個公共活動: 隱藏,收縮,複製Code
private event WindowHookDelegate InnerWindowCreated; private event WindowHookDelegate InnerWindowDestroyed; /// <summary> /// register for this event if you want to be informed about /// the creation of a window /// </summary> public event WindowHookDelegate WindowCreated { add { InnerWindowCreated += value; if (!iRun) { startThread(); } } remove { InnerWindowCreated -= value; // if no more listeners for the events if (null == InnerWindowCreated && null == InnerWindowDestroyed) iRun = false; } } /// <summary> /// register for this event, if you want to be informed about /// the destruction of a window /// </summary> public event WindowHookDelegate WindowDestroyed { add { InnerWindowDestroyed += value; if (!iRun) { startThread(); } } remove { InnerWindowDestroyed -= value; // if no more listeners for the events if (null == InnerWindowCreated && null == InnerWindowDestroyed) iRun = false; } } private void onWindowCreated(WindowHookEventArgs aArgs) { if (null != InnerWindowCreated) InnerWindowCreated(this, aArgs); } private void onWindowDestroyed(WindowHookEventArgs aArgs) { if (null != InnerWindowDestroyed) InnerWindowDestroyed(this, aArgs); } #endregion
當建立或銷燬一個新視窗時,會引發InnerWindowCreated和InnerWindowDestroyed事件。公共事件(WindowCreated, WindowDestroyed)將新的委託附加到內部事件並啟動和停止執行緒,這取決於是否仍然有相應內部事件的註冊偵聽器。onWindowCreated, onWindowDestroyed是用來防止NullReferenceException的。 在那之後,有一些PInvoke / DLLImports: 隱藏,複製Code
DllImport("user32.dll", EntryPoint = "EnumDesktopWindows", ExactSpelling = false, CharSet = CharSet.Auto, SetLastError = true)] private static extern bool _EnumDesktopWindows(IntPtr hDesktop, EnumDelegate lpEnumCallbackFunction, IntPtr lParam); DllImport("user32.dll", EntryPoint = "GetWindowText", ExactSpelling = false, CharSet = CharSet.Auto, SetLastError = true)] private static extern int _GetWindowText(IntPtr hWnd, StringBuilder lpWindowText, int nMaxCount); // GetClassName [DllImport("user32.dll", EntryPoint = "GetClassName", ExactSpelling = false, CharSet = CharSet.Auto, SetLastError = true)] private static extern int _GetClassName(IntPtr hwnd, StringBuilder lpClassName, int nMaxCount);
EnumDesktopWindows用於獲取所有開啟視窗的當前列表。GetWindowText用於獲取視窗的標題,GetClassName用於獲取視窗的類。 隱藏,複製Code
private void run()
基本上,它只是每500ms就輪詢一次新windows,並處理WindowHookNet的啟動和終止。 fireatedwindows掃描iNewWindowList中不在iOldWindowList中的條目,因此代表新的視窗。如果找到這樣一個條目,它將被放入一個特殊的列表中,掃描完成後,所有的“new window事件”將被觸發。 隱藏,複製Code
private void fireCreatedWindows() { iEventsToFire.Clear(); foreach (IntPtr tPtr in iNewWindowList.Keys) { // if the new list contains a key that is not // in the old list, that window has been created // add it into the fire list and to the "old" list if (!iOldWindowList.ContainsKey(tPtr)) { iEventsToFire.Add(iNewWindowList[tPtr]); //cLogger.Debug("new window found:" + //iEventsToFire[iEventsToFire.Count - 1]); } } // you need to remove / add things later, because // you are not allowed to alter the dictionary during iteration foreach (WindowHookEventArgs tArg in iEventsToFire) { //cLogger.Debug("sending WM_CREATE:" + tArg); iOldWindowList.Add(tArg.Handle, tArg); onWindowCreated(tArg); } }
完全相同的情況發生在fireClosedWindows,但反過來(掃描iOldWindowList條目不再在iNewWindowList)。 EnumWindowsProc是EnumDelegate的回撥方法,每個開啟的視窗都會呼叫它。它檢索視窗的類和視窗的標題,並建立WindowHookEventArgs物件。 歷史 添加了關於這個類工作方式和建立它的原因的大量資訊 本文轉載於:http://www.diyabc.com/frontweb/news5169.html