Setting up Hook on Windows messages

41,983

Solution 1

Here's a different approach: skip the SetWindowsHook API, and instead use WinEvents, which use SetWinEventHook instead. 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.)

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 MSDN for 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.

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 GetWindowThreadProcessId and only listen to events from that thread.

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.

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()); 
    }
}

Solution 2

For advice on how to use SetWindowHookEx, see SO question 214022. For working code in C# see 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.net is a good resource on the signatures your source coede needs in order to do that, but that has already been covered in question 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.aspx Hope that helps.

Share:
41,983

Related videos on Youtube

RanRag
Author by

RanRag

I am an undergraduate student who has completed his B-Tech in Information & Communication Technology(ICT), INDIA. I am just a normal guy who has recently found interest in programming. Initially I was a big Java fan but now I am a Python enthusiast. Currently, exploring Android App Development.

Updated on March 13, 2020

Comments

  • RanRag
    RanRag about 4 years

    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.

    I used Winspector and found out that whenever there is a track change in spotify WM_SETTEXT message is send.

    enter image description here

    For this I believe I have to set up a HOOK through my application to look for WM_SETTEXT message sent by the other application.

    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 setwindowshookex and also did some googling but am really lost as I have no background of C# and handling windows messages/events.

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

    • RanRag
      RanRag about 12 years
      @squelos : thanks that will help too.
    • user1703401
      user1703401 about 12 years
      The kind of hook you want to use requires a DLL that can be injected into the other process. You cannot write such a DLL in the C# language, the process won't have the CLR loaded and initialized. An unmanaged language is required, C, C++ or Delphi are typical choices.
    • RanRag
      RanRag about 12 years
      @HansPassant: So what zabulus suggested will not work.
    • RanRag
      RanRag about 12 years
      @HansPassant: Can you show me a sample code on how to achieve my task.
  • Cody Gray
    Cody Gray about 12 years
    The only hooks you can install from a managed (C#/.NET) application are a low-level keyboard hook (WH_LL_KEYBOARD) and a low-level mouse hook (WH_LL_MOUSE). Since neither of those will do what @Noob is in search of here, he'll have to call SetWindowsHookEx from an unmanaged application, written in a language like C or C++.
  • Cody Gray
    Cody Gray about 12 years
    The only answer so far that deserves an upvote... And yes, this is a far better option than a global hook anyway.
  • Raymond Chen
    Raymond Chen about 12 years
    Or just use the System.Windows.Automation namespace.
  • RanRag
    RanRag about 12 years
    Thanks a lot. Just what i was looking for.
  • Anders Lindén
    Anders Lindén over 6 years
    Would it be easy to create a WinEventProc this way that for instance reverses all window captions?