在Windows消息上设置Hook

时间:2012-03-12 10:41:57

标签: c# windows winapi

我正在尝试制作一个能够通知当前播放曲目的应用程序 用户的姓名和艺术家,我需要监控track change event

我使用Winspector并发现每当有曲目更改时 发送spotify WM_SETTEXT消息。

enter image description here

为此我相信我必须通过我的应用程序设置HOOK来查找其他应用程序发送的WM_SETTEXT消息。

现在,我遇到的问题是我无法获得任何可用的示例代码。我阅读了setwindowshookex的文档并进行了一些谷歌搜索,但由于我没有C#的背景和处理Windows消息/事件,所以我真的迷失了。

所以,如果你们可以为我提供一个小工作代码来围绕setting up hook在另一个应用程序上,或者如果你能指导我一些关于如何实现这个目标的好文章。

3 个答案:

答案 0 :(得分:49)

这是一种不同的方法:跳过SetWindowsHook API,而是使用WinEvents代替SetWinEventHook。这些有点类似于windows钩子,因为它们都涉及在特定事件中调用的回调函数,但是WinEvents更容易在C#中使用:您可以特定地将WinEvent传递给“out context”,这意味着事件已发布回到你自己的进程,所以你不需要一个单独的DLL。 (但是,您的代码需要在调用SetWinEventHook的同一线程上运行消息循环。)

事实证明,WinEvent支持的事件类型之一是“名称更改”事件,每当HWND的标题文本发生更改时,USER32会自动触发该事件,这似乎就是您要查找的内容。 (WinEvents还可用于跟踪焦点更改和各种类型的状态更改; see MSDN以获取更多信息。)当其内部UI更改时,其他控件也会触发它 - 例如,当列表项的文本时通过列表框变化,所以我们必须做一些过滤。

以下是一些示例代码,用于在桌面上的任何HWND上打印标题更改 - 例如,您会看到它会打印出通知,因为任务栏上的时钟中的文本会发生变化。您需要修改此代码以仅过滤您在Spotify中跟踪的HWND。此外,此代码侦听所有进程/线程上的名称更改;你应该使用GetWindowThreadProcessId从目标HWND获取threadID,并且只监听该线程中的事件。

另请注意,这是一种脆弱的方法;如果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()); 
    }
}

答案 1 :(得分: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希望有所帮助。

答案 2 :(得分:2)

您可以尝试在主窗体中覆盖WndProc,如下所示:

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

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