在 Windows 消息上设置挂钩

问题描述 投票:0回答:2

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

track change event
.

我用了

Winspector
,发现只要有音轨变化 spotify
WM_SETTEXT
消息已发送。

enter image description here

为此,我相信我必须通过我的应用程序设置一个

HOOK
来查找其他应用程序发送的
WM_SETTEXT
消息。

现在,我面临的问题是我无法获得任何可用的示例代码。我阅读了 setwindowshookex 的文档,也做了一些谷歌搜索,但我真的迷路了,因为我没有 C# 的背景和处理 Windows 消息/事件。

所以,如果你们能给我一个小的工作代码,让我在另一个应用程序上绕过

setting up hook
,或者如果你们能指导我阅读一些关于如何实现这一点的好文章。

c# windows winapi
2个回答
56
投票

这里有一个不同的方法:跳过 SetWindowsHook API,而是使用 WinEvents,后者使用 SetWinEventHook。这些有点类似于 Windows 挂钩,因为两者都涉及在特定事件时调用的回调函数,但 WinEvents 在 C# 中更容易使用:您可以指定 WinEvents 是“在上下文之外”传递的,这意味着事件被发布返回到您自己的进程,因此您不需要单独的 DLL。 (但是,您的代码确实需要在调用 SetWinEventHook 的同一线程上运行消息循环。)

事实证明,WinEvent 支持的事件类型之一是“名称更改”事件,只要 HWND 的标题文本发生更改,USER32 就会自动触发该事件,这似乎正是您要找的。 (WinEvents 也可用于跟踪焦点变化和各种类型的状态变化;有关更多信息,请参阅 MSDN。)当其他控件的内部 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 message 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()); 
    }
}

2
投票

有关如何使用 SetWindowHookEx 的建议,请参阅 SO 问题 214022。对于 C# 中的工作代码,请参阅 SO 问题 1811383

一般来说,如果你想从 C# 访问 WinAPI 函数,你需要做一个 Platform Invoke Call(简称 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 希望有帮助。

© www.soinside.com 2019 - 2024. All rights reserved.