|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Services
Chapters
Feature Zones
|
IntroductionThis article discusses the use of global system hooks in .NET applications. A reusable class library is developed along the way. You may have noticed other articles on using system hooks with P/Invoke on Code Project or other publications (see Background section below). This article is similar to those but there is a significant difference. This article covers using global system hooks in .NET whereas the other articles cover local system hooks. The ideas are similar but the implementation requirements are different. BackgroundIn case you are not familiar with the concept of system hooks in Windows, let me state a few brief descriptions:
There are several good articles which introduce the concept of system hooks. Rather than rehashing the introductory information here, I'll simply refer readers to those articles for background information on system hooks. If you're familiar with the concept of system hooks, then you should be able to get everything you need from this article.
What we are going to cover in this article is extending this information to create a global system hook which can be used by .NET classes. We will develop a class library in C# and a DLL in unmanaged C++ which together will accomplish this goal. Using the codeBefore we dig into developing this library, let's take a quick look at where we are headed. In this article, we will develop a class library that installs global system hooks and exposes the events processed by the hook as a .NET event of our hook class. To illustrate the usage of the system hook classes, we will create a mouse event hook and keyboard event hook in a Windows Forms application written in C#. The class library can be used to create any type of system hook. There are two that come pre-built: The usage and lifecycle of the mouseHook = new MouseHook(); // mouseHook is a member variable
Next, we wire up the mouseHook.MouseEvent += new MouseHook.MouseEventHandler(mouseHook_MouseEvent);
// ...
private void mouseHook_MouseEvent(MouseEvents mEvent, int x, int y)
{
string msg = string.Format("Mouse event: {0}: ({1},{2}).",
mEvent.ToString(), x, y);
AddText(msg); // Adds the message to the text box.
}
To start receiving mouse events, simply install the hook. mouseHook.InstallHook();
To stop receiving events, simply uninstall the hook. mouseHook.UninstallHook();
You can also call It is important that you uninstall the hook when your application exits. Leaving system hooks installed will slow message processing for all applications on the system at best. It could even cause one or more processes to become unstable. To put it in more technical terms: it's really really bad to forget this part. So, be sure to remove your system hooks when you are done with them. We ensure that we remove the system hooks in our sample application by adding a protected override void Dispose(bool disposing)
{
if (disposing)
{
if (mouseHook != null)
{
mouseHook.Dispose();
mouseHook = null;
}
// ...
}
}
That's all there is to using the class library. It comes with two system hook classes and is easily extendable. Building The LibraryThere are two major components of the library. The first part is a C# class library which you use directly in your application. That class library, in turn, uses an unmanaged C++ DLL internally to manage the system hooks directly. We'll first discuss developing the C++ part. Next, we'll cover how to use this library in C# to build a general hooking class. As we discuss the C++ / C# interaction, we'll pay particular attention to how the C++ methods and data types map to .NET methods and data types. You may be wondering why we need two libraries, especially an unmanaged C++ DLL. You may have noticed also that two of the reference articles mentioned in the background section of this article do not use any unmanaged code. To this I say, "Exactly! That's why I'm writing this article". When you think about how system hooks actually implement their functionality, it makes sense that we need unmanaged code. In order for a global system hook to work, Windows inserts your DLL into the process space of every running process. Since most processes are not .NET processes, they cannot execute .NET assemblies directly. We need an unmanaged code stub which Windows can insert into all the processes that will be hooked. The first order of business is to provide a mechanism to pass a .NET delegate into our C++ library. Thus, we defined the following function ( int SetUserHookCallback(HookProc userProc, UINT hookID)typedef void (CALLBACK *HookProc)(int code, WPARAM w, LPARAM l) The second parameter to private static extern SetCallBackResults
SetUserHookCallback(HookProcessedHandler hookCallback, HookTypes hookType)protected delegate void HookProcessedHandler(int code,
UIntPtr wparam, IntPtr lparam) public enum HookTypes
{
JournalRecord = 0,
JournalPlayback = 1,
// ...
KeyboardLL = 13,
MouseLL = 14
};
First, we import the Next, we need to pass // From windef.h: typedef UINT_PTR WPARAM; typedef LONG_PTR LPARAM; Therefore, we chose Now, let's see how the hook base class uses these imported methods to pass a callback function (delegate) to C++ which allows the C++ library to directly call into your system hook class instance. First, in the constructor, the public SystemHook(HookTypes type)
{
_type = type;
_processHandler = new HookProcessedHandler(InternalHookCallback);
SetUserHookCallback(_processHandler, _type);
}
The implementation of [MethodImpl(MethodImplOptions.NoInlining)]
private void InternalHookCallback(int code, UIntPtr wparam, IntPtr lparam)
{
try
{
HookCallback(code, wparam, lparam);
}
catch {}
}
We have added a method implementation attribute which tells the compiler to not inline this method. This is not optional. At least, it was required before I added the Now, let's look at how a derived class with a specific protected override void HookCallback(int code, UIntPtr wparam, IntPtr lparam)
{
if (MouseEvent == null)
{
return;
}
int x = 0, y = 0;
MouseEvents mEvent = (MouseEvents)wparam.ToUInt32();
switch(mEvent)
{
case MouseEvents.LeftButtonDown:
GetMousePosition(wparam, lparam, ref x, ref y);
break;
// ...
}
MouseEvent(mEvent, new Point(x, y));
}
First, note that this class defines an event In this method, we check that someone is actually listening to the event. If not, there is no reason to continue processing the event. Then, we convert our Next, after determining the type of event we have received, the class fires the event, and the consumer is notified of the type of mouse event and the location of the mouse during that event. A final note about converting the bool GetMousePosition(WPARAM wparam, LPARAM lparam, int & x, int & y) { MOUSEHOOKSTRUCT * pMouseStruct = (MOUSEHOOKSTRUCT *)lparam; x = pMouseStruct->pt.x; y = pMouseStruct->pt.y; return true; } Rather than attempting to map the private static extern bool InternalGetMousePosition(UIntPtr wparam,
IntPtr lparam, ref int x, ref int y)
By defining the integer parameters as RestrictionsSome hook types are not suited to this implementation of global hooks. I am currently considering work-arounds that will allow use of the restricted hook types. For now, don't add these types back into the library as they will result in application failures (often system-wide catastrophic failures). The next section expands on the reasons behind these restrictions and work-arounds. HookTypes.CallWindowProcedure HookTypes.CallWindowProret HookTypes.ComputerBasedTraining HookTypes.Debug HookTypes.ForegroundIdle HookTypes.JournalRecord HookTypes.JournalPlayback HookTypes.GetMessage HookTypes.SystemMessageFilter Two Types of Hooks - Those That Switch and Those That Don'tNote: This section is adapted from a follow-up post to the original article. You can read that post[^] in the discussion section below. I'll try to expand on why some hook types are in the restricted category and some are not. Please forgive me if I use terminology that is a little off. I haven't been able to find any documentation on this part of the topic so I'm making up the vocabulary as I go. Also, if you think I'm flat-out wrong, let me know. When Windows calls the callbacks passed to Hook types such as mouse and keyboard hooks are of the type that switch context before being called by Windows. The process goes something like this:
Hook types such as CBT hooks (window creation, etc.) do not switch contexts. For those types of hooks, the process is:
This should shed some light on why certain types of hooks work with the architecture of this library and some do not. Remember, this is what the library is attempting to do. Insert the following steps after steps 4 and 3 respectively in the above steps:
Steps 3 and 4 are doomed to fail for the non-switching types of hooks. Step 3 will fail because the corresponding managed callback will not be set for that application. Remember that the DLL uses global variables to track these managed delegates and that the hook DLL is loaded into every process space. But the values are only set in the process space of the hooking application. For all the others, those are null. Tim Sylvester points out in his post entitled "Other hook types" [^] that using a shared memory section[^] would solve this problem. This is true, but as Tim also notes, those managed delegate addresses are meaningless for any process except the hooking application. That means, they are meaningless and cannot be called for the duration of the callback's execution. That's trouble. Therefore, to use these callbacks for the types of hooks that do not do execution switching, you need some sort of interprocess communication. I have experimented with the idea of using out-of-process COM objects in the unmanaged DLL's hook callback for IPC. If you get this to work, I'd be interested in hearing about it. As for my attempts, they didn't turn out well. The basic reason was that it's difficult to properly initialize the COM apartment for the various processes and their threads ( There are ways to pull this off, I've no doubt. But I haven't attempted them yet because I think they are of limited utility. For example, the CBT hooks let you cancel a window creation if you wish. Imagine what must take place for this to work.
That's not impossible, but isn't pretty. I hope this has cleared up some of the mystery surrounding the allowed and restricted hook types in the library. Extras
A Final WarningLet me paraphrase Dr. Seuss in the famous children's book Fox in Sox in providing this final warning:
System hooks are powerful. And, with that power comes responsibility. When something goes wrong with system hooks, they don't just break your application. They can break every application running on your system. It is unlikely that it would actually come to that extreme. Nonetheless, you need to double and triple check your code when using system hooks. One technique that I have found useful for developing applications that use system hooks is to install a copy of your favorite development operating system and Visual Studio .NET in Microsoft Virtual PC [^]. Then develop your application in the virtual environment. That way, when your hook applications go wrong, they only take out the virtual instance of your operating system rather than your real one. I have had to restart my real OS when the virtual one crashed due to a hook error, but it is much less common. Note that if you have an MSDN Subscription [^], then Virtual PC is freely available through your subscription. History
| ||||||||||||||||||||