Click here to Skip to main content
6,594,932 members and growing! (15,206 online)
Email Password   helpLost your password?
General Reading » Hardware & System » General     Beginner License: The GNU General Public License (GPL)

Using Window Messages to Implement Global System Hooks in C#

By ChrisP1118

Explains how to implement any type of global system hook in C# by using an unmanaged C++ DLL and Windows messages.
VC7.1, C# 1.0.NET 1.1, Win2K, WinXP, Win2003, VistaVS.NET2003, Dev
Posted:3 May 2007
Views:74,467
Bookmarked:140 times
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
37 votes for this article.
Popularity: 7.62 Rating: 4.86 out of 5

1

2

3
5 votes, 13.5%
4
32 votes, 86.5%
5

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 (GPL)

About the Author

ChrisP1118


Member
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#.
Location: Burkina Faso Burkina Faso

Other popular Hardware & System articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 25 of 47 (Total in Forum: 47) (Refresh)FirstPrevNext
GeneralUsing from within service Pinmemberdaubsi13:20 29 Oct '09  
GeneralNot sure if your still monitoring but question Pinmemberdrewdb0:05 10 Sep '09  
GeneralSimilar concept using .NET was published Pinmembertbenami9:29 26 Jul '09  
GeneralA bug in the demo project [modified] Pinmembertestano6:17 23 Jul '09  
GeneralThe demo code doesn't work on vista [modified] Pinmembertestano5:28 23 Jul '09  
QuestionBlocking Keyboard Input PinmemberNirvanaBoy9:20 21 May '09  
QuestionHooking for a single application PinmemberMember 364369418:41 13 May '09  
GeneralDoesn't work on Windows Vista 64 bits PinmemberRevengerPT11:54 20 Mar '09  
AnswerRe: Doesn't work on Windows Vista 64 bits Pinmembercodesnipper.net23:42 28 Mar '09  
GeneralRe: Doesn't work on Windows Vista 64 bits Pinmembercers8:34 12 May '09  
AnswerRe: Doesn't work on Windows Vista 64 bits PinmemberMember 30945554:48 23 May '09  
GeneralRe: Doesn't work on Windows Vista 64 bits Pinmemberdaubsi13:16 29 Oct '09  
Questionlicensing and window open and close Pinmemberstyloverte11618:08 19 Jan '09  
Generalwhy not use #pragma data_seg() to store hwnd Pinmemberbborn4:15 15 Dec '08  
QuestionHooking window messages? PinmvpGiorgi Dalakishvili9:26 9 Dec '08  
GeneralUsing your lib Pinmembernadavg4:22 20 Nov '08  
QuestionUsing your class to cancel a particular window? Pinmembermpgjunky14:00 15 Sep '08  
QuestionHow to hook outlook windows message PinmemberAlex_Bob2:29 15 Jun '08  
QuestionThe main window slow down with MouseDLL hook? Pinmemberminhvc22:33 13 May '08  
QuestionThe Transperancy menu does not affect IE and it does not affect new windows, only existing ones Pinmemberasnat4:53 30 Apr '08  
QuestionHow would you get a handle of an application's sub item(s) rather than the main form PinmemberMember 285813811:12 21 Apr '08  
GeneralGet notified when a new process has started PinmemberDsypher2:37 26 Feb '08  
GeneralGetMsg stops firing after a while Pinmembercypher543215:45 23 Feb '08  
GeneralPossible use in a Console application? PinmemberMVWODOC14:29 10 Sep '07  
GeneralHint or Tooltip Pinmemberoakcool2:23 25 Aug '07  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 3 May 2007
Editor: Smitha Vijayan
Copyright 2007 by ChrisP1118
Everything else Copyright © CodeProject, 1999-2009
Web20 | Advertise on the Code Project