C# 在 Windows 消息上设置挂钩

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9665579/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 08:22:19  来源:igfitidea点击:

Setting up Hook on Windows messages

c#windowswinapi

提问by RanRag

I am trying to make an application which will notify current playing track's name and artist to the user for that I need to monitor the track change event.

我正在尝试制作一个应用程序,它将向用户通知当前播放曲目的名称和艺术家,因为我需要监视track change event.

I used Winspectorand found out that whenever there is a track change in spotify WM_SETTEXTmessage is send.

我使用Winspector并发现,每当WM_SETTEXT发送spotify消息中的轨道更改时。

enter image description here

在此处输入图片说明

For this I believe I have to set up a HOOKthrough my application to look for WM_SETTEXTmessage sent by the other application.

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

Now, the problem I am facing is I am not able to get any working sample code to work with. I read the documentation of setwindowshookexand also did some googling but am really lost as I have no background of C# and handling windows messages/events.

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

So, If you guys can provide me a small working code to wrap my head around setting up hookon another application or if you can direct me to some nice article on how to achieve this.

所以,如果你们可以给我提供一个小的工作代码来让我setting up hook了解另一个应用程序,或者如果你们可以指导我阅读一些关于如何实现这一点的好文章。

采纳答案by BrendanMcK

Here's a different approach: skip the SetWindowsHook API, and instead use WinEvents, which use SetWinEventHookinstead. These are somewhat similar to the windows hooks, in that both involve a callback function that is called at specific events, but WinEvents are far easier to use from C#: you can specific that WinEvents are delivered "out context", meaning the events are posted back to your own process, so you don't need a separate DLL. (Your code does need to run a message loop on the same thread that called SetWinEventHook, however.)

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

It turns out that one of the type of events that WinEvent supports is a 'name change' event, which is automatically fired by USER32 whenever the title text of a HWND changes, which seems is what you are looking for. (WinEvents can also be used to track focus changes and various types of state changes; see MSDNfor more information.) It's also fired by other controls when their internal UI changes - eg by a listbox when the text of a list item changes, so we have to do some filtering.

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

Here's some sample code that prints out title changes on any HWND on the desktop - you'll see it print out a notification as the text in the clock on the taskbar changes, for example. You'll want to modify this code to filter for just the HWND you're tracking in Spotify. Also, this code listens to name changes on all processes/threads; you should get the threadID from the target HWND using GetWindowThreadProcessIdand only listen to events from that thread.

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

Note also that this is something of a fragile approach; if Spotify changes how it displays the text, or changes the format of it, you'll need to modify your code to keep up with its changes.

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

回答by Treb

For advice on how to use SetWindowHookEx, see SO question 214022. For working code in C# see SO question 1811383.

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

In general, if you want to access WinAPI functions from C#, you need to do a Platform Invoke Call(short PInvoke). pinvoke.netis a good resource on the signatures your source coede needs in order to do that, but that has already been covered in question 1811383.

一般来说,如果你想从 C# 访问 WinAPI 函数,你需要做一个平台调用调用(简称PInvoke)。pinvoke.net是一个很好的资源,介绍了您的源 coede 需要的签名,但这已在问题 1811383 中进行了介绍。

Since I never understood the whole Windows messaging queue, I don't know if the method proposed by zabulus will work when the message originates in a different process. But I found some example code here: http://en.serialcoder.net/Winforms/527/533/Interoperability%20Win32/How%20can%20I%20use%20%20Hooks%20%20in%20.NET.aspxHope that helps.

由于我从来没有了解过整个 Windows 消息队列,不知道 zabulus 提出的方法在消息起源于不同进程时是否有效。但我在这里找到了一些示例代码:http: //en.serialcoder.net/Winforms/527/533/Interoperability%20Win32/How%20can%20I%20use%20%20Hooks%20%20in%20.NET.aspxHope这有帮助。

回答by zabulus

You can try to override WndProc in your main form, something like this:

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

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

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