Click here to Skip to main content
15,868,141 members
Articles / Programming Languages / C#
Article

Using Window Messages to Implement Global System Hooks in C#

Rate me:
Please Sign up or sign in to vote.
4.88/5 (68 votes)
3 May 2007GPL311 min read 373.3K   14.9K   254   85
Explains how to implement any type of global system hook in C# by using an unmanaged C++ DLL and Windows messages.

Screenshot - WilsonGlobalSystemHooks1.jpg

Screenshot - WilsonGlobalSystemHooks1.jpg

Introduction

Global system hooks allow an application to intercept Windows messages intended for other applications. This has always been difficult (impossible, according to MSDN) to implement in C#. This article attempts to implement global system hooks by creating a DLL wrapper in C++ that posts messages to the hooking application's message queue. Put simply, this lets you implement any type of global Windows hook from managed code.

Why Global System Hooks and C# Don't Play Well Together

Global system hooks have always been a problem for .NET developers. To understand why, we first need to discuss how hooks work at a system level and how Windows processes these event notifications.

Normally, an application implements hooks by creating a callback function and informing Windows of this function. When Windows processes a hookable event, such as mouse movement or the creation of a window, it calls this callback function, which the hooking application handles accordingly. With local hooks (when an application hooks other events taking place in the same process), this callback function can be located anywhere, and local hooks can be written pretty easily in C# -- a quick look around CodeProject reveals several examples. Unfortunately, Windows is not so permissive with global system hooks, which allow one process to intercept events from another process. Windows insists that the callback functions for global system hooks be located in DLLs. Neither C# nor VB.NET are able to produce standard Windows DLLs, which means they're also unable to process global system hooks -- the MSDN library even explicitly says global system hooks cannot be handled within managed code applications. How discouraging!

Luckily, C# programmers are a clever and crafty bunch. Michael Kennedy, in his CodeProject article "Global System Hooks in .NET", came up with a workaround. Using C++, he created a DLL that essentially worked as a wrapper to managed code. The C++ DLL registered a callback function with Windows. When a hook notification arrived in that callback, it called a managed code function. Using this technique, he was able to trap several hook notifications, such as low-level keyboard and mouse events. The problem he discovered, though, was that Windows changes the execution context when sending out certain hook notifications. When Windows hooked a low-level mouse or keyboard event, it called any hooking application from within the context of the hooking application. But for most other events, the callback function was called from the context of the hooked application. This essentially meant that his wrapper functions couldn't be used with most Windows hooks.

The Workaround

I've created my own workaround to this problem, although as you'll see below, it comes with some definite caveats. In a nutshell, what I've done is (like Kennedy) to create a C++ DLL which contains the actual callback function. However, the DLL doesn't just serve as a wrapper to a managed code delegate. Instead, when it receives a hook notification, it posts a custom Windows message to the managed application's Windows message queue. This message can then be intercepted in the C# program's WndProc event. So, just as a window regularly receives messages like WM_MOUSEMOVE, your managed application will now also receive messages like WILSON_HOOK_SHELL_WINDOWCREATED.

The Projects

The code consists of three projects. First, there's the GlobalCbtHook, a C++ project which creates the DLL used for the callback functions. Second, there's GlobalHooksTest, a simple application designed to demonstrate this code, which implements WH_SHELL and WH_CBT hooks. The third (and coolest) of the three projects is TransparencyMenu. This is also a demonstration project, showing an actual practical application of global system hooks.

GlobalCbtHook -- the DLL Project

This project is deceptively simple. There are, however, a few quirks to my programming, which have created the potential for some bugs in your application, as described below. One other note: with some minor exceptions, all the similarly-named functions in the DLL do the same thing, just for different types of hooks. That is, InitializeCbtHook and InitializeShellHook work in the same way -- so in my explanations below, I use ABC as a sort of generic hook.

Warning One: SetProp and GetProp

Because Windows injects the callback function into all running processes, there will always be multiple instances of this code loaded. Yet they all need to share at least one piece of information, namely, the handle to the window that needs to receive the hook event notifications. After several frustrating hours reading up on shared memory, I decided I wanted an easier solution. That's when the MSDN documentation threw me a lifesaver in the form of GetProp and SetProp.

GetProp and SetProp are API functions that allow you to associate any type of handle with any window. That is, if you have a window handle (hWnd), you can associate any other handle with it, and in such a way that any application can access that data. Luckily, there's one window handle that's essentially constant in any Windows session, the desktop handle. So, in order to store the handle to the window receiving hook events, I use the desktop window's properties. This is kind of a hack, but works pretty reliably and effectively. The InitializeAbcHook functions checks to see if there is already a handle named WILSON_HOOK_HWND_ABC already associated with the desktop window. Assuming there's not, this handle is set to the handle of the window that will receive the hook events.

This code is in InitializeCbtHook, showing GetProp:

if (GetProp(GetDesktopWindow(), "WILSON_HOOK_HWND_CBT") != NULL)
{
  SendNotifyMessage((HWND)GetProp(GetDesktopWindow(), 
      "WILSON_HOOK_HWND_CBT"), 
      RegisterWindowMessage("WILSON_HOOK_CBT_REPLACED"), 0, 0);
}

SetProp(GetDesktopWindow(), "WILSON_HOOK_HWND_CBT", destination);

And SetProp is used in CbtHookCallback to retreive the window handle:

HWND dstWnd = (HWND)GetProp(GetDesktopWindow(),
               "WILSON_HOOK_HWND_CBT");

There is one major problem to this approach: it means that only one application can be hooking these events at a time. When a second application (that uses the same DLL) starts hooking events, a message (WILSON_HOOK_CBT_REPLACED) is sent to the first application, informing it that it's now out of luck and no longer hooking anything. So, if you use this code in your own software, it'd be a good idea to recompile it, changing the WILSON_HOOK strings to something different, so that your application will not conflict with any other software using this approach.

Warning Two: Return Values

The other major problem with my particular approach to hooking is that it doesn't allow you to return a value when a hook callback is called. Normally, a hook callback function is allowed to return a value, which can affect whatever event is happening that generated the hook. For instance, when the CBT CREATEWND hook is raised, setting a non-zero return value prevents the window from being created. This functionality could be used to prevent certain classes or types of windows from being created. However, in its current form, my solution doesn't allow you to set a return value. There is, theoretically, a fairly easy workaround for this. I use the SendNotifyMessage in the C++ DLL to send the message to the hooking application, which means the DLL just posts a message and then doesn't wait around to see if the hooking application actually processes it. However, you could instead use the SendMessage command, which forces the DLL to wait for the hooking application to process the hook and return the value set in Message.Result in the managed code. The downside to this approach, and the reason I didn't implement it this way, is that it can very easily lead to system crashes. If your managed code encounters an unhandled exception, execution won't return to the calling SendMessage function, which in turn hangs the application being hooked. A dozen hard reboots in the span of about 20 minutes convinced me of the wisdom of SendNotifyMessage.

The RegisterWindowMessage Function

My code uses the RegisterWindowMessage function. This is an API function which generates a unique message ID that can be used among multiple applications. When one application registers a message with a certain name, any other application that registers a message of the same name will get the same message ID in response. All the messages posted by the DLL use message IDs generated by this function. Thus, managed code must also call this function to know what message IDs to watch for in WndProc.

Inside the DLL

The C++ DLL contains code to handle eight different types of hooks. If you are so inclined, you should be able to follow the model and easily create additional code to handle the other hook types. For each type of hook, there are three functions in the DLL. These three functions are more-or-less the same between all the different hook types.

InitializeAbcHook is an exported function that's called from managed code. The first parameter is the ID of the thread to hook. Setting this to zero will hook all processes -- giving you a global hook. You could also use this to only hook events from one thread, which in many scenarios would be far more efficient than hooking events in all threads. The second parameter is the handle of the window that hook events will be broadcast to. This should be the handle of a form or a control. The InitializeAbcHook function checks to see if a message handle has already been stored (a.k.a., another application is already hooking using our DLL), then stores this handle, and finally installs the hook using this line:

hookAbc = SetWindowsHookEx(WH_ABC, (HOOKPROC)AbcHookCallback, 
                                g_appInstance, threadID);

UninitializeAbcHook just removes the hook. Make sure your managed code application calls this before quitting -- if you don't, the DLL will keep hooking, even though your application (that's supposed to receive the hooks) has ended. This can lead to some major problems, as well as needlessly degrades performance.

AbcHookCallback is the actual callback function, and varies a bit depending on the hook type. For the simplest hooks, like mouse and keyboard hooks, this function just sends the necessary message to the queue of the hooking application, and then continues the hook chain by calling CallNextHookEx. For hooks like CBT or Shell, there are multiple types of events that could be occuring -- such as ACTIVATE or CREATEWND. The callback function determines which type of sub-hook is being called, and then raises the appropriate event. For CallWndProc and GetMsg, there is too much data to send to the hooking application using only one message. For these two hooks, after all, there are four pieces of data we want to know in our managed application: the handle of the hooked window, the message that was hooked, and the lParam and wParam values -- however, when sending our own messages to the hooking application, we can only send data in lParam and wParam. So these two callback functions send two messages, one with the handle and intercepted message, the other with the intercepted lParam and wParam. They also have a check to prevent infinite recursion -- GetMsg sends a message to the hooking application, which is in turn intercepted by the same callback function that posted the message.

The Managed Code: GlobalHooks.cs

I've created a class called GlobalHooks which functions as an intermediary between a managed application and the DLL, making it easy to implement global hooks in your own projects. The class contains an abstract internal class called Hook, as well as eight subclasses of Hook, one for each type of hook supported.

To use global system hooks in your own code, the first thing to do is create an instance of the GlobalHooks class:

private GlobalHooks _GlobalHooks;

When you instantiate this class, you must pass it a window handle:

_GlobalHooks = new GlobalHooks(this.Handle);

You also need to override the WndProc function to allow the GlobalHooks function to process all incoming messages. This ProcessWindowMessage function checks for messages coming from the DLL, indicating a hook notification has been raised.

protected override void WndProc(ref Message m)
{
    if (_GlobalHooks != null)
        _GlobalHooks.ProcessWindowMessage(ref m);

    base.WndProc (ref m);
}

Next, you need to create some event handlers for whatever you want to hook. For instance, to receive notification every time a top-level window is created, create an event handler for the GlobalHooks.Shell.WindowCreated, as follows:

_GlobalHooks.Shell.WindowCreated +=
           new GlobalHooks.WindowEventHandler(Shell_WindowCreated);

To begin hooking events, you must call the Start function in the appropriate class.

_GlobalHooks.Shell.Start();

Just as important are the corresponding Stop functions, which must be called when you want to stop hooking events. Make sure you stop hooking events before your application terminates.

_GlobalHooks.Shell.Stop();

That's all it takes!

Final Thoughts

Despite the potential pitfalls, the technique described here has worked quite well for me when I've needed to hook global events. However, if you're creating an application that only needs to hook low-level mouse or keyboard events, Michael Kennedy's code might be better suited for you. Also, the CallWndProc and GetMsg hooks are both pretty inefficient, as they essentially duplicate every message generated by Windows (creating a copy to send to the hooking application). If your application only needs to hook one or two messages, you can easily rewrite the DLL to only post notification messages to the hooking application when the hooked program receives those one or two messages you're looking for.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Burkina Faso Burkina Faso
I currently live in a small town in Burkina Faso, where I spend some of my free time (during the hours when there's electricity) playing around with C#. For a bit more about my less nerdy activities, check out http://chrisburkina.blogspot.com or http://www.youtube.com/watch?v=Rw1Pb7hlIWw -- neither of them, however, has anything to do with C#.

Comments and Discussions

 
GeneralThe demo code doesn't work on vista [modified] Pin
testano23-Jul-09 4:28
testano23-Jul-09 4:28 
QuestionBlocking Keyboard Input Pin
NirvanaBoy21-May-09 8:20
NirvanaBoy21-May-09 8:20 
QuestionHooking for a single application Pin
Debashis Pal13-May-09 17:41
Debashis Pal13-May-09 17:41 
GeneralDoesn't work on Windows Vista 64 bits Pin
RevengerPT20-Mar-09 10:54
RevengerPT20-Mar-09 10:54 
AnswerRe: Doesn't work on Windows Vista 64 bits Pin
c-romeo28-Mar-09 22:42
professionalc-romeo28-Mar-09 22:42 
GeneralRe: Doesn't work on Windows Vista 64 bits Pin
cers12-May-09 7:34
cers12-May-09 7:34 
AnswerRe: Doesn't work on Windows Vista 64 bits Pin
ShashkinAS23-May-09 3:48
ShashkinAS23-May-09 3:48 
GeneralRe: Doesn't work on Windows Vista 64 bits Pin
daubsi29-Oct-09 12:16
daubsi29-Oct-09 12:16 
You have to compile for x64 not x86...
GeneralRe: Doesn't work on Windows Vista 64 bits Pin
cdebel8-Mar-11 5:09
cdebel8-Mar-11 5:09 
GeneralRe: Doesn't work on Windows Vista 64 bits Pin
The-Great-Kazoo15-Apr-11 9:01
professionalThe-Great-Kazoo15-Apr-11 9:01 
GeneralRe: Doesn't work on Windows Vista 64 bits [modified] Pin
Jelle Vergeer23-Aug-11 11:03
Jelle Vergeer23-Aug-11 11:03 
GeneralRe: Doesn't work on Windows Vista 64 bits [modified] Pin
Member 849417413-Feb-12 22:58
Member 849417413-Feb-12 22:58 
GeneralRe: Doesn't work on Windows Vista 64 bits [modified] Pin
jehonathangt11-Jun-12 15:37
jehonathangt11-Jun-12 15:37 
GeneralRe: Doesn't work on Windows Vista 64 bits [modified] Pin
Jelle Vergeer12-Aug-12 22:09
Jelle Vergeer12-Aug-12 22:09 
Questionlicensing and window open and close Pin
styloverte11619-Jan-09 17:08
styloverte11619-Jan-09 17:08 
Generalwhy not use #pragma data_seg() to store hwnd Pin
bborn15-Dec-08 3:15
bborn15-Dec-08 3:15 
QuestionHooking window messages? Pin
Giorgi Dalakishvili9-Dec-08 8:26
mentorGiorgi Dalakishvili9-Dec-08 8:26 
GeneralUsing your lib Pin
nadavg20-Nov-08 3:22
nadavg20-Nov-08 3:22 
QuestionUsing your class to cancel a particular window? Pin
mpgjunky15-Sep-08 13:00
mpgjunky15-Sep-08 13:00 
QuestionHow to hook outlook windows message Pin
Alex_Bob15-Jun-08 1:29
Alex_Bob15-Jun-08 1:29 
QuestionThe main window slow down with MouseDLL hook? Pin
minhvc13-May-08 21:33
minhvc13-May-08 21:33 
QuestionThe Transperancy menu does not affect IE and it does not affect new windows, only existing ones Pin
asnat30-Apr-08 3:53
asnat30-Apr-08 3:53 
QuestionHow would you get a handle of an application's sub item(s) rather than the main form Pin
Member 285813821-Apr-08 10:12
Member 285813821-Apr-08 10:12 
GeneralGet notified when a new process has started Pin
Dsypher26-Feb-08 1:37
Dsypher26-Feb-08 1:37 
GeneralGetMsg stops firing after a while Pin
cypher543223-Feb-08 14:45
cypher543223-Feb-08 14:45 

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.