Click here to Skip to main content
15,885,159 members
Articles / Programming Languages / C#

How to Write a Managed Global Hook for Window Creation and Destruction

Rate me:
Please Sign up or sign in to vote.
4.92/5 (14 votes)
6 Aug 2009CPOL3 min read 52K   1.9K   34   12
Create a global hook that sends WM_CREATE and WM_DESTROY messages

Introduction

In the career of almost any programmer, there comes the time when he faces the following problem:

His application is in need of some sort of shiny feature that requires him to get notified when a new window gets opened or closed.

Back in the days of C++, this was rather simple and straightforward. Just register your application as a hook for WM_DESTROY and WM_CREATE.

Unfortunately, this has changed with the introduction of .NET and managed applications and things have become more difficult to implement.

In this article, we will discuss an implementation, that allows you to register for events that signal you when a new window has been created or an old window was destroyed.

Background

I am currently writing an application that performs operations on other windows and was searching for an easy way to get a callback whenever a new window is created. I am doing this in C#.

After literally days of searching the internet, I realized that there is a way implemented in .NET to get such a callback.

There is a way in the Win32 API via the calling the method SetWindowsHookEx. However, for this to work with all windows (and not just windows, that are created within your AppDomain), you would need to register a so called "global hook", which is not possible in .NET.
For a global hook to work, you would need to inject a DLL into other processes. This is not possible with managed DLLs!

Therefore I came up with a solution that does nearly the same as SetWindowsHookEx but is limited to only send events when a window is created or destroyed.

For more background information, see this link.

Using the Code

My solution is contained within a single file and consists of two classes:

C#
public class WindowHookEventArgs
{
    public IntPtr Handle = IntPtr.Zero;
    public string WindowTitle = null;
    public string WindowClass = null;

    public override string ToString()
    { 
        return "[WindowHookEventArgs|Title:" + WindowTitle + "|Class:"
                + WindowClass + "|Handle:" + Handle + "]";
    }
}

This class just contains the information about the raised event and provides you with the window handle (most commonly referred to as hWnd in C++).

The other class is WindowHookNet and is a singleton. I chose to implement it as a singleton to avoid performance problems when a dozen or more instances of this class keep pulling for all open windows.

First of all, you have two delegates:

C#
public delegate void WindowHookDelegate(object aSender, WindowHookEventArgs aArgs);
private delegate bool EnumDelegate(IntPtr hWnd, int lParam);

WindowHookDelegate is used to register for the events raised by WindowHookNet.
EnumDelegate is used internally to retrieve a list of all windows open at the moment.

Next there are four events. Two private and two public events:

C#
private event WindowHookDelegate InnerWindowCreated;
private event WindowHookDelegate InnerWindowDestroyed;

/// <summary>
/// register for this event if you want to be informed about
/// the creation of a window
/// </summary>
public event WindowHookDelegate WindowCreated
{
    add
    {
        InnerWindowCreated += value;
        if (!iRun)
        {
            startThread();
        }
    }
    remove
    {
        InnerWindowCreated -= value;

        // if no more listeners for the events
        if (null == InnerWindowCreated &&
            null == InnerWindowDestroyed)
            iRun = false;
    }
}

/// <summary>
/// register for this event, if you want to be informed about
/// the destruction of a window
/// </summary>
public event WindowHookDelegate WindowDestroyed
{
    add
    {
        InnerWindowDestroyed += value;
        if (!iRun)
        {
            startThread();
        }
    }
    remove
    {
        InnerWindowDestroyed -= value;

        // if no more listeners for the events
        if (null == InnerWindowCreated &&
            null == InnerWindowDestroyed)
            iRun = false;
    }
}

private void onWindowCreated(WindowHookEventArgs aArgs)
{
    if (null != InnerWindowCreated)
        InnerWindowCreated(this, aArgs);
}

private void onWindowDestroyed(WindowHookEventArgs aArgs)
{
    if (null != InnerWindowDestroyed)
        InnerWindowDestroyed(this, aArgs);
}
#endregion 

The InnerWindowCreated, InnerWindowDestroyed events are raised when a new window is created or destroyed. The public events (WindowCreated, WindowDestroyed) attach new delegates to the inner events and start and stop the thread depending on whether or not there are still registered listeners for the corresponding inner event. onWindowCreated, onWindowDestroyed are used to prevent NullReferenceException.

After that, there are some PInvoke / DLLImports:

C#
DllImport("user32.dll", EntryPoint = "EnumDesktopWindows",
ExactSpelling = false, CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool _EnumDesktopWindows(IntPtr hDesktop,
EnumDelegate lpEnumCallbackFunction, IntPtr lParam);

DllImport("user32.dll", EntryPoint = "GetWindowText",
ExactSpelling = false, CharSet = CharSet.Auto, SetLastError = true)]
private static extern int _GetWindowText(IntPtr hWnd,
StringBuilder lpWindowText, int nMaxCount);

// GetClassName
[DllImport("user32.dll", EntryPoint = "GetClassName", ExactSpelling = false,
            CharSet = CharSet.Auto, SetLastError = true)]
private static extern int _GetClassName(IntPtr hwnd, StringBuilder lpClassName,
            int nMaxCount);

EnumDesktopWindows is used to get the current list of all opened windows.
GetWindowText is used to get a window's title and GetClassName is used to get a window's class.

C#
private void run()

This basically just polls for new windows every 500ms and handles termination and startup of WindowHookNet.

fireCreatedWindows scans the iNewWindowList for entries that are not in the iOldWindowList and therefore represent new windows. If such an entry is found, it is put into a special list and after the scan is completed, all "new window events" are fired.

C#
private void fireCreatedWindows()
        {
            iEventsToFire.Clear();
            foreach (IntPtr tPtr in iNewWindowList.Keys)
            {
                // if the new list contains a key that is not
                // in the old list, that window has been created
                // add it into the fire list and to the "old" list
                if (!iOldWindowList.ContainsKey(tPtr))
                {
                    iEventsToFire.Add(iNewWindowList[tPtr]);
                    //cLogger.Debug("new window found:" + 
                    //iEventsToFire[iEventsToFire.Count - 1]);
                }
            }

            // you need to remove / add things later, because
            // you are not allowed to alter the dictionary during iteration
            foreach (WindowHookEventArgs tArg in iEventsToFire)
            {
                //cLogger.Debug("sending WM_CREATE:" + tArg);
                iOldWindowList.Add(tArg.Handle, tArg);
                onWindowCreated(tArg);
            }
        }

Exactly the same happens in fireClosedWindows but the other way round (the iOldWindowList is scanned for entries that are no longer in the iNewWindowList).

EnumWindowsProc is the callback method for EnumDelegate and is called for every open window. It retrieves the window's class and the window's title and creates the WindowHookEventArgs object.

History

  • Added a whole lot of information about the way this class works and why I created it

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Germany Germany
I once upon a time was a software developer and developed Java applications for a small enterprise in my home town.
Nowadays I no longer work as a software developer but instead keep on coding for fun.

Comments and Discussions

 
QuestionWorking example ? Pin
Member 1410459731-Aug-22 11:25
Member 1410459731-Aug-22 11:25 
QuestionThanks! Pin
Member 123036165-Feb-17 16:18
Member 123036165-Feb-17 16:18 
Suggestion[My vote of 2] Polling is bad Pin
blade__20-Sep-15 23:05
blade__20-Sep-15 23:05 
GeneralRe: [My vote of 2] Polling is bad Pin
TigerNinja_2-Mar-23 14:33
TigerNinja_2-Mar-23 14:33 
BugThread can't be re-used when re-registering events Pin
salonent24-May-15 23:05
salonent24-May-15 23:05 
QuestionEvents not fired Pin
Swarnamala_V2-Jul-12 0:42
Swarnamala_V2-Jul-12 0:42 
AnswerRe: Events not fired Pin
d8m1k3-Feb-13 2:44
d8m1k3-Feb-13 2:44 
Generalit doesn't work for me Pin
crhoatyh1-Jun-11 20:21
crhoatyh1-Jun-11 20:21 
GeneralRe: it doesn't work for me Pin
medic48911-Jun-11 23:15
medic48911-Jun-11 23:15 
GeneralMy vote of 5 Pin
wkblalock1-Jun-11 2:07
wkblalock1-Jun-11 2:07 
QuestionHow to use these classes Pin
Harry16-Dec-09 7:32
Harry16-Dec-09 7:32 
AnswerRe: How to use these classes Pin
medic489122-Dec-09 8:50
medic489122-Dec-09 8:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.