This article will discuss the global system hook in. NET application. To this end, I developed a reusable class library and created the corresponding sample program (see the figure below).
You may have noticed another article about using system hooks. This paper is similar to it, but there are important differences. This article will discuss the global system hook in. NET, while other articles only discuss local system linking. These ideas are similar, but the implementation requirements are different.
Second, the background
If you are not familiar with the concept of Windows system hook, let me briefly describe it:
? The system hook allows you to insert a callback function-it intercepts certain Windows messages (for example, mouse-related messages).
? The local system hook is a system hook-it is only called when the specified message is processed by a separate thread.
? A global system hook is a system hook-it is called when a specified message is processed by any application on the whole system.
There are several good articles introducing the concept of system hook. Here, I don't want to collect any more introductory information. I just refer the reader to the following background article on system hooks. If you are familiar with the concept of system hook, you can get anything you can from this article.
? MSDN Library's knowledge about hooks.
? Dino esposito's front window hooks in. NET framework.
? The application of Don Kackman to Hook in C#.
What we will discuss in this article is to extend this information to create a global system hook-it can be used. NET class. We will use C# and a DLL and unmanaged C++ to develop a class library-they will accomplish this goal together.
Third, the use of code.
Before we delve into this library, let's take a quick look at our goals. In this article, we will develop a class library-it installs global system hooks and exposes these events handled by hooks as. NET events of our hook class. To illustrate the usage of this system hook class, we will create a mouse event hook and a keyboard event hook in a Windows Forms application written in C#.
These class libraries can be used to create any kind of system hooks, including two precompiled hooks-mousehook and KeyboardHook. We also include specific versions of these classes, called MouseHookExt and KeyboardHookExt. According to the models set by these classes, you can easily build system hooks for any 15 hook event type in Win32 API. In addition, there is a compiled HTML help file in this complete class library-it archives these classes. Be sure to read this help file if you decide to use this library in your application.
The usage and life cycle of the MouseHook class are very simple. First, we create an instance of the MouseHook class.
mouseHook = new mouseHook(); //mouseHook is a member variable.
Next, we bind the MouseEvent event to a class-level method.
Mouse hook. Mouse event+= new mouse hook. mouse eventhandler(mouse hook _ mouse event);
// ...
private void mouse hook _ mouse event(mouse events me event,int x,int y){
Stringsmsg = string.format ("Mouse event:: (,)." ,mEvent。 ToString(),x,y);
add text(msg); //Add a message to the text box
}
To start receiving mouse events, simply install the hook below.
Mouse hook. install hook();
To stop receiving events, simply uninstall this hook.
Mouse hook. uninstall hook();
You can also call Dispose to unload the hook.
It is important to uninstall this hook when your application exits. Installing system hooks all the time will slow down the message processing speed of all applications in the system. It can even make one or more processes very unstable. Therefore, be sure to remove the system hook when you are finished using it. We are sure that the system hook will be removed in our sample application-by adding a Dispose call to the Dispose method of the form.
Protected coverage void Dispose(bool disposing) {
If (disposal) {
If (mouseHook! = null) {
Mouse hook. dispose();
mouseHook = null
}
// ...
}
}
This is the case when using this class library. There are two system hook classes in this class library, which are easy to extend.
Fourth, build a library.
This library has two main components. The first part is a C# class library-you can use it directly in your application. Conversely, the class library uses unmanaged C++ DLL internally to directly manage system hooks. We will discuss developing this C++ part first. Next, we will discuss how to use this library in C# to build a general hook class. Just as we discussed C++/C# interaction, we will pay special attention to how C++ methods and data types map to. NET methods and data types.
You may wonder why we need two libraries, especially an unmanaged C++ DLL. You may also notice two reference articles mentioned in the background of this article, which do not use any unmanaged code. To this end, my answer is, "Yes! This is why I wrote this article. " It is very important that we need unmanaged code when you consider how system hooks actually realize their functions. To make the global system hook work, Windows inserts your DLL into the process space of each running process. Because most processes are not. NET processes, which cannot be directly executed. NET assembly. We need an unmanaged code proxy-Windows can plug it into all the processes that will be hooked.
The first is to provide a mechanism to pass the. NET proxy to our C++ library. In this way, we define the following functions (SetUserHookCallback) and function pointers (HookProc) in C++ language.
int setuserhoockcallback(hook proc user proc,UINT hookID)
typedef void(CALLBACK * hook proc)(int code,WPARAM w,LPARAM l)
The second parameter of SetUserHookCallback is the hook type-this function pointer will use it. Now, we must use C# to define the corresponding methods and proxies to use this code. Here's how we map it to C#.
Private static external SetCallBackResults
setuserhoockcallback(HookProcessedHandler hook callback,HookTypes hookType)
Protected delegate Void HookProcessedHandler (intcode, UIntPtr wparam, IntPtr lparam)
Public enumeration hook type {
JournalRecord = 0,
JournalPlayback = 1
// ...
Keyboard LL = 13,
MouseLL = 14
};
First, we use the DllImport property to import the SetUserHookCallback function as a static external method of our abstract basic hook class SystemHook. To do this, we must map some external data types. First, we must create a proxy as our function pointer. This is achieved by defining the HookProcessHandler above. We need a function with C++ signature (int, WPARAM, LPARAM). In Visual Studio. NET C++ compiler, int is the same as in C#. That is to say, in C++ and C#, int is Int32. This is not always the case. Some compilers regard C++ int as Int 16. We insist on using Visual Studio. NET C++ compiler to realize this project, so we don't have to worry about another definition brought by compiler differences.
Next, we need to pass WPARAM and LPARAM values in C#. These are actually pointers to the UINT and LONG values of C++ respectively. In C#, they are pointers to uint and int. If you are not sure what WPARAM is, you can right-click in the C++ code and select "Go to Definition" to query. This will lead you to the definition in windef.h
//from windef.h:
typedef UINT _ PTR WPARAM
typedef LONG _ PTR LPARAM
Therefore, we choose the system. UIntPtr and system. IntPtr is our variable type-they correspond to WPARAM and LPARAM types respectively, when they are used in C#.
Now, let's see how the hook base class uses these imported methods to pass the callback function (proxy) to C++- it allows the c++ library to directly call an instance of your system hook class. First, in the constructor, the SystemHook class creates a proxy for the private method InternalHookCallback-it matches the proxy signature of the HookProcessedHandler. Then, it passes the proxy and its HookType to the C++ library to register the callback function using the SetUserHookCallback method, as described above. The following is its code implementation:
Common system hook (HookTypes type) (
_ type = type
_ process handler = new HookProcessedHandler(InternalHookCallback);
setuserhoockcallback(_ process handler,_ type);
}
The implementation of InternalHookCallback is very simple. InternalHookCallback wraps it with a catch-all try/catch block and only passes it to the call of the abstract method HookCallback. This will simplify the implementation in derived classes and protect C++ code. Remember, once everything is ready, this C++ hook will call this method directly.
[MethodImpl(MethodImplOptions。 NoInlining)]
private void InternalHookCallback(int code,UIntPtr wparam,IntPtr lparam){
attempt
Catch {}
}
We added a method implementation attribute-it told the compiler not to inline this method. This is not optional. At least, I need it before I add try/catch. It seems that for some reason, the compiler is trying to inline this method-this will bring all kinds of troubles to the agent that wraps it. Then, the C++ layer will call back and the application will crash.
Now, let's see how derived classes use a specific HookType to receive and handle hook events. The following is the HookCallback method implementation of the virtual MouseHook class:
protected override void hook callback(int code,UIntPtr wparam,IntPtr lparam){
if (MouseEvent == null)
int x = 0,y = 0;
mouse events mEvent =(mouse events)wparam。 touint 32();
Switch (mevent) (
Case mouse event. Left-click:
GetMousePosition(wparam,lparam,ref x,ref y);
Break;
// ...
}
mouse event(me event,new Point(x,y));
}
First, notice that this class defines an event mouseevent-this class triggers this event when it receives a hook event. This class converts data from WPARAM and LPARAM types to meaningful mouse event data in. NET before the event that triggered it. This will save consumers from having to worry about interpreting these data structures. This class uses the imported GetMousePosition function to convert these values, which we defined in C++ DLL. To do this, please refer to the discussion in the following paragraphs.
In this method, we check whether anyone is listening to the event. If not, there is no need to continue to deal with this incident. Then, we convert WPARAM to MouseEvents enumeration type. We have carefully constructed MouseEvents enumerations to exactly match their corresponding constants in C ++. This allows us to simply convert the value of a pointer to an enumeration type. Note, however, that this conversion will succeed even if the value of WPARAM does not match the enumeration value. The value of mEvent will only be undefined (not null, just out of the range of enumerated values). To this end, please analyze it systematically. Enumeration is a well-defined method.
Next, after determining the type of event we received, this class activates this event and informs consumers of the type of mouse event and the location of the mouse in the event.
Finally, pay attention to the conversion of WPARAM and LPARAM values: for each type of event, the values and meanings of these variables are different. Therefore, we must interpret these values differently in each hook type. I choose C++ to realize this transformation, instead of trying to imitate complex C++ structures and pointers with C#. For example, the previous class used a C++ function named GetMousePosition. Here are the methods in C++ DLL:
bool get mouse position(WPARAM WPARAM,LPARAM lparam,int ampx,int ampy) {
MOUSEHOOKSTRUCT * pMouseStruct =(MOUSEHOOKSTRUCT *)lparam;
x = pMouseStruct-& gt; pt.x
y = pMouseStruct-& gt; pt.y
Return true
}
Instead of trying to map the MOUSEHOOKSTRUCT structure pointer to C#, we simply send it back to the C++ layer temporarily to extract the value we need. Note that because we need to return some values from this call, we pass integers as reference variables. This maps directly to int* in C#. However, we can overload this behavior and import this method by choosing the correct signature.
Private static external bool internal getmouseposition (uintptr wparam, IntPtr lparam, ref int x, ref int y)
By defining the integer parameter as ref int, we get the value passed to us through C++ reference. We can also use out int if we want.
Verb (abbreviation for verb) restriction
Some hook types are not suitable for implementing global hooks. I am currently considering a solution-allowing the use of restricted hook types. So far, don't add these types back to the library, because they will lead to application failures (usually catastrophic system-wide failures). The next section will focus on the reasons behind these limitations and solutions.
Hook type Call window procedure
Hook type CallWindowProret
Hook type Computer-based training
Hook type shakedown test/debug
Hook type Idle prospect
Hook type Log record
Hook type Log playback
Hook type GetMessage
Hook type System message filter
Six, two types of hooks
In this section, I will try to explain why some hook types are limited to a certain category and others are not. Please forgive me if my terminology is a little biased. I haven't found any documents about this sub-topic, so I made up my own vocabulary. Besides, if you think I am wrong, please tell me.
When Windows calls the callback function passed to SetWindowsHookEx (), it will be called in different ways due to different hook types. There are basically two situations: hooks that switch execution contexts and hooks that do not switch execution contexts. In other words, the hook callback function is executed in the application process space where the hook is placed and the hook callback function is executed in the hooked application process space.
Hook types such as mouse and keyboard hooks switch contexts before being called by Windows. The whole process is roughly as follows:
1. application x gets focus and executes.
2. The user presses a key.
3.Windows takes over the context from application X and switches the execution context to the hooked application.
4.Windows calls the hook callback function with the key message parameter in the application process space where the hook is placed.
5.Windows takes over the context from the hooked application and switches the execution context back to application X. ..
6.Windows puts the message in the message queue of application X. ..
7. After a while, when application X executes, it takes messages from its own message queue and calls its internal button (or releases or presses) processor.
8. Application X continues to execute. ...
Such as CBT hook (window creation, and so on. ) Do not switch contexts. For these types of hooks, the process is roughly as follows:
1. application x gets focus and executes.
2. Application X creates a window.
3.Windows calls the hook callback function with the CBT event message parameter in the process space of application X. ..
4. Application X continues to execute. ...
This should explain why some types of hooks can work with this library structure and some can't. Remember, this is exactly what the library should do. After step 4 and step 3 above, insert the following steps respectively:
1.Windows calls the hook callback function.
2. The target callback function is executed in an unmanaged DLL.
3. The target callback function looks for its corresponding managed calling agent.
4. Execute the managed agent with the appropriate parameters.
5. The target callback function returns and executes hook processing corresponding to the specified message.
The third and fourth steps are doomed to fail because they don't switch hook types. The third step will fail because the corresponding managed callback function will not be set for the application. Remember, this DLL uses global variables to track these managed agents, and the hook DLL is loaded into each process space. However, this value is only set in the application process space where the hook is placed. For other cases, they are all empty.
Tim Sylvester pointed out in his article Other hook types that using * * * shared memory will solve this problem. This is true, but as Tim pointed out, those managed proxy addresses are meaningless to any process except the hooked application. This means that they are meaningless and cannot be called during the execution of the callback function. That will bring you trouble.
Therefore, in order to use these callback functions for hook types that do not perform context switching, you need some kind of interprocess communication.
I tried this idea-using out-of-process COM objects in the unmanaged DLL hook callback function of IPC. If you can make this method work, I will be glad to know. As for my attempt, the result is not satisfactory. The basic reason is that it is difficult to correctly initialize COM units (CoInitialize(NULL)) for various processes and their threads. This is a basic requirement before using COM objects.
I don't doubt that there must be a way to solve this problem. But I haven't tried them yet, because I think their usefulness is limited. For example, a CBT hook allows you to cancel window creation if you want. You can imagine what will happen to make this work.
1. Hook callback function starts to execute.
2. Call the corresponding hook callback function in the unmanaged hook DLL.
3. Execute the application that must be routed back to the main hook.
4. The application must decide whether to allow this creation.
5. The call must be routed back to the hook callback function that is still running.
6. The hook callback function in the unmanaged hook DLL receives the operation to be performed from the main hook application.
7. The hook callback function in the unmanaged hook DLL takes appropriate actions on the CBT hook call.
8. Complete the execution of the hook callback function.
This is not impossible, but it is not good. I hope this will dispel the mystery surrounding the types of hooks allowed and restricted in this library.
Seven. others
? Library documentation: We have included a relatively complete code documentation about the ManagedHooks class library. When compiled in the Document build configuration, this is converted into standard help XML through Visual Studio.NET. Finally, we use NDoc to convert it into compiled HTML Help (CHM). You can read this help file, just click the Hooks.chm file in scheme's solution browser, or find the downloadable ZIP file related to this article.
? Enhance IntelliSense: If you are not familiar with how Visual Studio.NET uses compiled XML files (output before NDoc) to enhance IntelliSense for reference library projects, let me briefly introduce it. If you decide to use the class library in your application, you can consider copying the stable build version of the library to the location where you want to reference it. At the same time, copy the XML document file (system hooks \ managed hooks \ bin \ debug \ Kennedy. ManagedHooks.xml) to the same location. When you add a reference to the library, Visual Studio.NET will automatically read the file and use it to add an IntelliSense document. This is very useful, especially for third-party libraries like this.
? Unit test: I believe that all libraries should have corresponding unit tests. Because I am a partner and software engineer of a company (mainly responsible for unit testing of software in IN). NET environment), no one will be surprised. Therefore, you will find a unit test project in a solution called ManagedHooksTests. In order to run this unit test, you need to download and install Harnessit-this download is a free trial version of our commercial unit test software. In this unit test, I paid special attention to this point-here, invalid parameters of methods may cause C++ memory exceptions. Although this library is quite simple, this unit test can really help me find some mistakes in some more subtle situations.
? Unmanaged/Managed Debugging: One of the toughest things about mixed solutions (for example, managed and unmanaged code in this article) is debugging. If you want to step through C++ code or set breakpoints in C++ code, you must start unmanaged debugging. This is an engineering setting in visual Studio.NET. Please note that you can debug the managed and unmanaged layers very smoothly in one step, but during debugging, unmanaged debugging will seriously reduce the loading time and execution speed of the application.
Eight, the last warning
Obviously, the system hook is quite powerful; However, the use of this power should be responsible. When system hooks go wrong, they not only crash the application. They can crash all applications running in the current system. But the possibility of reaching this level is generally very small. However, when using system hooks, you need to check your code carefully.
I found a useful technology that can be used to develop applications-it uses system hooks to install your favorite development operating system and visualize a copy of Studio.NET on Microsoft's virtual PC. Then you can develop your application in this virtual environment. In this way, when your hook applications have errors, they will only exit the virtual instance of your operating system, not your real operating system. When this virtual operating system crashed due to a hook error, I had to restart my real operating system, but this did not happen very often.