2006-05-30
| Table of Contents: |
| Rate This Article: | Add This Article To: |
( Page 2 of 3 )
You might have noticed earlier that I included the SetLastError attribute in the DllImport attribute. Here's where that attribute comes into play. Notice that the code uses the Marshal.GetLastWin32Error() method to return an error number.
However, an error number isn't helpful. Consequently, you use the FormatMessage() function (see the downloadable code for a definition of this Win32 API call) to change the error number into a human readable string. As with most things, you must marshal the string from the unmanaged environment into a managed string using the technique shown. Finally, the code throws an exception that includes both a basic error message and the Windows-specific error message.
The ClearTheHook() method follows the same pattern as SetTheHook(). It checks the handle, calls the UnhookWindowsHookEx() function, and handles any errors. The big difference is that when the UnhookWindowsHookEx() function is successful, you must set HookHandle back to IntPtr.Zero to show that the handle is no longer in use.
Processing Messages
You have to tell Windows where to send the messages it collects for you. In native code terms, the function that receives the messages is a callback function. You supply the address of the callback function as second argument to the SetWindowsHookEx() function. However, the .NET Framework doesn't understand callback functions. You use a delegate for the task instead. The delegate defines a type, which you then use to create a method that handles the message processing.
Listing 3 shows both the delegate and the message processing code.
Listing 3: Providing a Method to Handle the Message Traffic
// This delegate provides the callback functionality
// required by the hook.
private delegate Int32 ProcMessage(
Int32 nCode, IntPtr wParam, IntPtr lParam);
// This function performs the required message processing.
private Int32 ProcessMessage(
Int32 nCode, IntPtr wParam, IntPtr lParam)
{
// Only process positive codes in your handling coding.
// Any negative code is for another hook.
if (nCode < 0)
{
CallNextHookEx(HookHandle, nCode, wParam, lParam);
return 0;
}
// Pass the arguments for the current message to the
// caller.
HookEventArgs ThisEvent = new HookEventArgs();
ThisEvent.nCode = nCode;
ThisEvent.wParam = wParam;
ThisEvent.lParam = lParam;
// Invoke the event.
Hooked.Invoke(this, ThisEvent);
// Always call the next hook in line.
return CallNextHookEx(HookHandle, nCode, wParam, lParam);
}
The delegate shown in this example works for every kind of message you can hook. It must define a code, the actual message number, a wParam, and an lParam. The wParam and lParam are two containers used to hold information about the message. This information varies by hook type. I'll show an example a little later. For now, all you need to know is that you should handle these two variables as pointers to some unmanaged data.
Always make your processing function as short as possible. You don't want to place a speed bump on all of Windows because you're performing too much work in your message handler.
This example works well for most messages. In this case, the callback function receives information from Windows. When the nCode value is less than 0, you must pass it on to the next hook in the chain without doing anything. Your code should only process positive values.
This brings up the CallNextHookEx() function, which is another Windows API call that you must define to create a hook. Here's the managed code for the CallNextHookEx() function.
// You must call the next hook in line after processing a
// hook message or Windows may not work as anticipated.
[DllImport("User32.DLL", SetLastError = true)]
private static extern Int32 CallNextHookEx(
IntPtr hhk, Int32 nCode, IntPtr wParam, IntPtr lParam);
The input for this function is the same as the delegate — with one important exception. You must pass the handle that the SetWindowsHookEx() call returns. This handle identifies the current hook, so that Windows knows which hook to call next.
Notice that the message processing method, ProcessMessage(), doesn't perform much work. The code that creates a new HookEventArgs objects, however, is essential if you actually want to use the message data. (You can find the definition for the HookEventArgs in the downloadable code.) Remember, you're receiving pointers to unmanaged data, not managed data. Always copy the data as shown to ensure you can access it within your application.
Once ProcessMessage() collects the data into an HookEventArgs object, it invokes an event, and then exits by calling the next hook in line using the CallNextHookEx() function. The Hooked event relies on a delegate that defines a custom template, as shown here.
// This delegate provides a means for passing information
// back to the caller.
public delegate void HookEventHandler(
Object Sender, HookEventArgs e);
// This is the event that the caller handles to receive a
// message.
public event HookEventHandler Hooked;
This is the common pattern used for .NET applications. However, it relies on the special HookEventArgs class to pass the message arguments to the application. Make sure you define the delegate and event as shown to make invoking the event easy.
![]() |
|


