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.