2012-03-12 95 views
19

我正在嘗試製作一個應用程序,它將通知當前播放曲目的名稱和藝術家給用戶,因爲我需要監視track change event在Windows消息中設置Hook

我用Winspector,發現只要有一個跟蹤改變 spotify WM_SETTEXT消息發送。

enter image description here

對於這一點,我相信我有通過我的應用程序建立一個HOOK尋找被其他應用程序發送WM_SETTEXT消息。

現在,我面臨的問題是我無法獲得任何有效的示例代碼。我閱讀了setwindowshookex的文檔,也做了一些Google搜索,但由於我沒有C#的背景和處理Windows消息/事件,所以我真的迷失了方向。

所以,如果你們可以給我一個小的工作代碼,在另一個應用程序上圍繞我的腦袋setting up hook,或者如果你可以指導我一些關於如何實現這一目標的好文章。

+0

@squelos:謝謝,這也會有所幫助。 – RanRag 2012-03-12 11:09:09

+2

你想要使用的鉤子類型需要一個可以注入其他進程的DLL。你不能用C#語言編寫這樣一個DLL,該進程將不會加載和初始化CLR。一個非託管語言是必需的,C,C++或Delphi是典型的選擇。 – 2012-03-12 13:42:54

+0

@HansPassant:那麼'zabulus'建議不起作用。 – RanRag 2012-03-12 13:45:21

回答

42

這裏有一個不同的方法:跳過SetWindowsHook API,而是使用WinEvents,它使用SetWinEventHook代替。這些有點類似於windows鉤子,因爲它們都涉及在特定事件中調用的回調函數,但是WinEvents在C#中使用起來要容易得多:您可以指定WinEvent是「out context」,這意味着事件已發佈回到你自己的過程,所以你不需要單獨的DLL。 (然而,你的代碼確實需要在調用SetWinEventHook的同一線程上運行消息循環。)

事實證明,WinEvent支持的事件類型之一是「名稱更改」事件,該事件被自動觸發每當HWND的標題文本改變時,都由USER32執行,這看起來就是你正在尋找的東西。 (WinEvents也可用於跟蹤焦點更改和各種類型的狀態更改;有關更多信息,請參閱)。當內部UI發生更改時,它也會被其他控件觸發 - 例如,當列表項的文本發生更改時,通過列表框,我們必須做一些過濾。

下面是一些示例代碼,可以在桌面上的任何HWND上打印標題更改 - 例如,您會看到它會在任務欄上的時鐘文本發生更改時打印出通知。你需要修改這段代碼來過濾你在Spotify中跟蹤的HWND。此外,這段代碼監聽所有進程/線程上的名稱更改;您應該使用GetWindowThreadProcessId從目標HWND獲取線程ID,並只偵聽來自該線程的事件。

還要注意這是一種脆弱的方法;如果Spotify改變其顯示文本的方式或更改其格式,則需要修改代碼以跟上其更改。

using System; 
using System.Windows; 
using System.Windows.Forms; 
using System.Runtime.InteropServices; 

class NameChangeTracker 
{ 
    delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, 
     IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime); 

    [DllImport("user32.dll")] 
    static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr 
     hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, 
     uint idThread, uint dwFlags); 

    [DllImport("user32.dll")] 
    static extern bool UnhookWinEvent(IntPtr hWinEventHook); 

    const uint EVENT_OBJECT_NAMECHANGE = 0x800C; 
    const uint WINEVENT_OUTOFCONTEXT = 0; 

    // Need to ensure delegate is not collected while we're using it, 
    // storing it in a class field is simplest way to do this. 
    static WinEventDelegate procDelegate = new WinEventDelegate(WinEventProc); 

    public static void Main() 
    { 
     // Listen for name change changes across all processes/threads on current desktop... 
     IntPtr hhook = SetWinEventHook(EVENT_OBJECT_NAMECHANGE, EVENT_OBJECT_NAMECHANGE, IntPtr.Zero, 
       procDelegate, 0, 0, WINEVENT_OUTOFCONTEXT); 

     // MessageBox provides the necessary mesage loop that SetWinEventHook requires. 
     // In real-world code, use a regular message loop (GetMessage/TranslateMessage/ 
     // DispatchMessage etc or equivalent.) 
     MessageBox.Show("Tracking name changes on HWNDs, close message box to exit."); 

     UnhookWinEvent(hhook); 
    } 

    static void WinEventProc(IntPtr hWinEventHook, uint eventType, 
     IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) 
    { 
     // filter out non-HWND namechanges... (eg. items within a listbox) 
     if(idObject != 0 || idChild != 0) 
     { 
      return; 
     } 
     Console.WriteLine("Text of hwnd changed {0:x8}", hwnd.ToInt32()); 
    } 
} 
+0

到目前爲止唯一的答案值得upvote ...是的,無論如何,這是一個比全局鉤子更好的選擇。 – 2012-03-13 09:03:22

+0

@Brendan:謝謝我會試試你的代碼並回復你。 – RanRag 2012-03-13 10:24:45

+1

或者只是使用'System.Windows.Automation'命名空間。 – 2012-03-13 13:30:13

2

有關如何使用SetWindowHookEx的建議,請參閱SO question 214022。有關C#中的工作代碼,請參閱SO question 1811383

一般來說,如果你想從C#訪問WinAPI的功能,你需要做一個平臺調用呼叫(短的PInvoke)。 pinvoke.net是您源系統需要簽名的好資源,但這已經在問題1811383中討論過了。

由於我從來不瞭解整個Windows消息隊列,我不知道該方法當信息源於不同的過程時,zabulus提出的建議將起作用。但我在這裏找到了一些示例代碼: http://en.serialcoder.net/Winforms/527/533/Interoperability%20Win32/How%20can%20I%20use%20%20Hooks%20%20in%20.NET.aspx希望有所幫助。

+0

謝謝我會考慮他們並回復你。 – RanRag 2012-03-12 11:08:25

+0

您可以從託管(C#/ .NET)應用程序安裝的** only **鉤子是低級別鍵盤鉤子('WH_LL_KEYBOARD')和低級別的鼠標鉤子('WH_LL_MOUSE')。由於這兩者都不會執行@Noob在這裏搜索的內容,所以他必須從非託管應用程序調用'SetWindowsHookEx',用C或C++語言編寫。 – 2012-03-13 09:02:49

3

你可以試着重寫的WndProc在你的主要形式,是這樣的:

protected override void WndProc(ref Message m) 
{ 
    base.WndProc(ref m); 

    if (m.Msg == WM_SETTEXT) 
    { 
      // Call to your logic here 
    } 
} 
+0

但我如何將它附加到特定的過程。我的意思是我在哪裏傳遞特定於我的應用程序的'hwnd'參數。 – RanRag 2012-03-12 11:23:24

+0

你有你的應用程序的來源?如果是,請查找傳遞給Application.Run方法的表單類。並將我的代碼粘貼到其身上 – zabulus 2012-03-12 11:49:42

+0

這是我的代碼的當前狀態http://pastebin.com/sVKm2Y5a – RanRag 2012-03-12 13:17:27