2006-05-30
| Table of Contents: |
| Rate This Article: | Add This Article To: |
( Page 1 of 3 )
Once you know that windows and messages exist, you find out that getting the messages isn't always easy. Sometimes, you want to track messages that the CLR doesn't support... and that's when the fun begins.
Download the code for this article here.
In the previous article in this series (see Working with Windows Messages in .NET), you discovered a whole world of messages that the .NET Framework doesn't even capture. These messages can signal all kinds of events, request changes, and even can end your application.
Unfortunately, the techniques described in that article only work for messages that the CLR passes to your application. Sometimes, you want to track messages that the CLR doesn't support. In some cases, these messages perform mundane tasks, such as displaying a dialog box in another application. Other times, you want to create custom messages to perform inter-application coordination. This article looks at both message types.
Windows messages work using a linked list of listeners. Every time another application wants to listen to a particular message, it makes a special Win32 API call, and Windows adds it to the list. The linked list sends each message to a particular listener. When that listener is done with the message, it passes on the message to the next listener in the list. Hooks come in two forms: thread and global. Thread-level hooks only monitor messages for the current thread, while those at the global level listen to all applications. In addition, hooks come in a variety of forms, just as messages do. All of the Windows hook types begin with WH_.
You can see a complete list of Windows hook types and determine whether you can use them at the thread or global level here.
Most of the time, you want a thread hook that affects only your application. When you create a thread hook, Windows filters out messages for other applications. You don't have to worry about your code finding its way into other environments; it stays with the current application. This is an important consideration, because you can't create a global hook using the .NET Framework; at least, not a global hook that's problem-free. A global hook requires use of a shared global memory space for certain requirements and it simply isn't possible to create such a memory space in .NET, at least not conveniently.
It's possible to create a local hook within the current application, or you can create it using a separate DLL. When working with a global hook, the hook routine must appear in a separate DLL. I created this example in a separate DLL and placed it in a class, but this isn't a requirement for a local hook. (We'll discuss global hooks in the next article in this series, "Globally Hooking Windows Messages in .NET.")
Hooking and Unhooking Methods
The example begins by looking at the process for setting and clearing a hook. A hook exists within a first-in-first-out (FIFO) list structure that Windows maintains. When you add a hook to the list, it receives the message first and passes the message on to the next hook in the chain.
It's important that your code perform this task quickly, because Windows has many messages to process. Setting and clearing hooks relies on two Win32 API calls, SetWindowsHookEx() and UnhookWindowsHookEx(). Listing 1 shows the managed prototypes you need for your application (make sure you include the System.Runtime.InteropServices namespace in your application code).
Listing 1: Creating the SetWindowsHookEx() and UnhookWindowsHookEx() Function Definitions
// This function sets a Windows hook and lets us trap the
// messages.
[DllImport("User32.DLL", SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(
SWH_ID idHook,
ProcMessage lpfn,
IntPtr hMod,
Int32 dwThreadId);
// This function unhooks an existing Windows hook.
[DllImport("User32.DLL", SetLastError = true)]
private static extern Boolean UnhookWindowsHookEx(IntPtr hhk);
The first argument is actually a number, when you work with the SetWindowsHookEx() function. However, since this is .NET, there isn't any reason to make things difficult. You can create an enumeration to make it easier to locate the values you need. The Set Windows Hook Identifier (SWH_ID) enumeration contains a list of the hook values, as defined in the WinUser.h file (see the downloadable code for details).
The second argument is a pointer to a message processing function (more on that later). The third argument contains an instance handle for the application. Since there's only one instance, in most cases, you'll normally pass IntPtr.Zero for this value. The third value is the identifier of the current application thread — not the DLL thread. Windows has to know which thread to use to collect messages for you. If you set this value to 0, the hook that SetWindowsHookEx() creates is global, rather than for a specific thread.
The return value of the SetWindowsHookEx() function is a handle to the hook. You must save this value, because you need it to perform a number of tasks. The most important of these tasks is calling the UnhookWindowsHookEx() function. You must unhook your hook before the application exits. Otherwise, a pointer to it still exists in the list afterward, and there isn't any way to access it. Because this pointer doesn't point to anything, Windows can start acting strangely or even crash. Unlike working with the .NET Framework, the Windows API doesn't provide much of a safety net. Any garbage you leave lying around stays in Windows until you reboot it.
The existence of the two Win32 API functions isn't enough to make them useable in your application. You must perform both error detection and error control. Consequently, the example places the calls into two functions: SetTheHook() and ClearTheHook() that perform the tasks their names imply. Listing 2 shows the code for these two functions.
Listing 2: Setting and Clearing the Hook
// Before an application can receive any hooked messages, it
// must create the hook.
public Boolean SetTheHook(SWH_ID HookType, Int32 ThreadId)
{
// Verify that we haven't already set the hook.
if (HookHandle != IntPtr.Zero)
{
// Signify that the hook is already set.
throw new Exception("Hook Already Set");
}
// Set the hook and save the hook pointer.
HookHandle = SetWindowsHookEx(
HookType,
PM,
IntPtr.Zero,
ThreadId);
// Verify that the hook has set.
if (HookHandle == IntPtr.Zero)
{
Int32 ErrNum; // Error number.
String ErrStr; // Error message.
// Retrieve the error.
ErrNum = Marshal.GetLastWin32Error();
// Change it into a string.
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
IntPtr.Zero,
ErrNum,
0,
out ErrStr,
0,
0);
// If the hook fails to set, then we need to tell
// the caller about it.
throw new Exception("Failed to Hook\r\n" + ErrStr);
}
// Signify success.
return true;
}
// It's essential to clear the hook before the application exits.
public Boolean ClearTheHook()
{
// Verify there is a hook to clear.
if (HookHandle == IntPtr.Zero)
{
// Signify that the hook isn't set.
throw new Exception("Hook Isn't Set");
}
// Clear the hook.
if (!UnhookWindowsHookEx(HookHandle))
{
... Error Trapping the Same as SetTheHook ...
}
// Clear the current hook.
HookHandle = IntPtr.Zero;
// Signify success.
return true;
}
The SetTheHook() method begins by checking HookHandle. You must always verify the current state of the variable that holds the hook handle. Windows will always oblige your request to create a new hook using the SetWindowsHookEx() function, so you must verify the state of the variable to ensure you aren't overwriting it with a new handle (making it impossible to unhook the previous hook).
Once the code verifies that HookHandle isn't in use, it creates a new hook handle. Unfortunately, this call isn't always successful, so you have to provide error trapping. If HookHandle still equals IntPtr.Zero (remember that you can't use null on an IntPtr variable), the call failed. Unlike the .NET Framework, Windows won't throw an exception.
![]() |
|


