Current location - Quotes Website - Personality signature - What are system hooks?
What are system hooks?

1. Introduction

This article will discuss the use of global system hooks in .NET applications. To do this, I developed a reusable class library and created a corresponding sample program (see image below).

You may have noticed another article about using system hooks. This article is similar but has important differences. This article will discuss using global system hooks in .NET, while the other articles only discuss local system hooks. The ideas are similar, but the implementation requirements are different.

2. Background

If you are not familiar with the concept of Windows system hooks, let me give a brief description: A system hook allows you to insert a callback function - it intercepts certain Windows messages (for example, mouse-related messages). A local system hook is a system hook - it is only called when the specified message is handled by a single thread. A global system hook is a system hook - it is called when the specified message is processed by any application on the entire system.

There are several good articles introducing the concept of system hooks. Rather than recollect this introductory information, I simply refer the reader to the following article for some background information on system hooks. If you are familiar with the concept of system hooks, you will be able to take away everything you can from this article. Knowledge about hooks in MSDN library. "Cutting Edge-Windows Hooks in the .NET Framework" by Dino Esposito. "Using Hooks in C#" by Don Kackman.

What we are going to discuss 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 using C# and a DLL and unmanaged C - together they will accomplish this goal.

3. Using Code

Before we dive into developing this library, let's take a quick look at our goals. In this article, we will develop a class library that installs global system hooks and exposes the events handled by these hooks as a .NET event in our hook class. To illustrate the use 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 libraries can be used to create any type of system hook. There are two precompiled hooks - MouseHook and KeyboardHook. We've also included specific versions of these classes, called MouseHookExt and KeyboardHookExt respectively. Based on the model set by these classes, you can easily build system hooks - for any of the 15 hook event types in the Win32 API. In addition, there is a compiled HTML help file in this complete class library - which archives these classes. Please make sure you read this help file - if you decide to use this library in your application.

The usage and life cycle of the MouseHook class are quite 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.

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); //Add a message to the text box

}

To start receiving mouse events, simply install the following hook.

mouseHook.InstallHook();

To stop receiving events, simply uninstall the hook.

mouseHook.UninstallHook();

You can also call Dispose to uninstall this hook.

It is important to uninstall this hook when your application exits. Leaving system hooks installed will slow down message processing for all applications on the system. It can even make one or more processes very unstable. Therefore, please make sure to remove your system hooks when you are done using them. We determined that the system hook would be removed in our sample application by adding a Dispose call to the Form's Dispose method.

protected override void Dispose(bool disposing) {

if (disposing) {

if (mouseHook != null) {

mouseHook.Dispose();

mouseHook = null;

}

// ...

}

}

This is the case when using this class library. There are two system hook classes in this library and are fairly easy to extend.

4. Building the library

This library has two main components. The first part is a C# library - you can use it directly in your application. The library, in turn, internally uses an unmanaged C DLL to manage system hooks directly. We'll discuss developing this C part first. Next, we will discuss how to use this library to build a general hook class in C#. Just as we discuss C/C# interaction, we'll pay special attention to how 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 also notice that the two reference articles mentioned in the background section of this article do not use any unmanaged code. To that, my answer is, "Yes! That's exactly why I wrote this article." When you think about how system hooks actually implement their functionality, it's important that we need unmanaged code. In order for a global system hook to work, Windows inserts your DLL into the process space of each running process. Since most processes are not .NET processes, they cannot directly execute .NET assemblies. We need an unmanaged code proxy - one that Windows can insert into all processes that are going to be hooked.

The first is to provide a mechanism to pass a .NET proxy to our C library.

In this way, we define the following function (SetUserHookCallback) and function pointer (HookProc) in C language.

int SetUserHookCallback(HookProc userProc, UINT hookID)

typedef void (CALLBACK *HookProc)(int code, WPARAM w, LPARAM l)

SetUserHookCallback's The second parameter is the hook type - this function pointer will use it. Now, we have to define the appropriate methods and proxies in C# to use this code. Here's how we map it to C#.

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 use the DllImport attribute to import the SetUserHookCallback function as a static of our abstract base hook class SystemHook external method. To do this we have to map some external data types. First, we must create a proxy to serve as our function pointer. This is achieved by defining the HookProcessHandler above. We need a function whose C signature is (int, WPARAM, LPARAM). In the Visual Studio .NET C compiler, int is the same as in C#. In other words, int is Int32 in C and C#. It wasn't always this way. Some compilers treat C int as Int16. We insist on using the Visual Studio .NET C compiler to implement this project, so we don't have to worry about additional definitions caused by compiler differences.

Next, we need to pass the WPARAM and LPARAM values ??using C#. These are indeed pointers, pointing to C UINT and LONG values ??respectively. In C# terms, they are pointers to uints and ints. If you're not sure yet what WPARAM is, you can query it by right-clicking in C code and selecting "Go to definition". This will lead you to the definition in windef.h.

//From windef.h:

typedef UINT_PTR WPARAM;

typedef LONG_PTR LPARAM;

Therefore, we choose System.UIntPtr and System.IntPtr as our variable types - they correspond to the WPARAM and LPARAM types respectively, as they are used in C#.

Now, let's look at how the hook base class uses these imported methods to pass a callback function (proxy) into C - which allows the C library to call instances of your system hook classes directly. First, in the constructor, the SystemHook class creates a proxy to the private method InternalHookCallback - which matches the HookProcessedHandler proxy signature. It then passes this proxy and its HookType to the C library to register the callback function using the SetUserHookCallback method, as discussed above. The following is its code implementation:

public SystemHook(HookTypes type){

_type = type;

_processHandler = new HookProcessedHandler(InternalHookCallback);

SetUserHookCallback(_processHandler, _type);

}

The implementation of InternalHookCallback is quite simple. InternalHookCallback only passes calls to the abstract method HookCallback while wrapping it with a catch-all try/catch block. This simplifies implementation in derived classes and protects C code. Remember, this C hook will call this method directly once everything is ready.

[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 - it tells the compiler not to inline this method. This is not optional. At least, that was what was needed before I added try/catch. It looks like, for some reason, the compiler is trying to inline this method - which will cause all kinds of trouble for the agent that wraps it. The C layer will then call back and the application will crash.

Now, let's take a look at how a derived class uses a specific HookType to receive and handle hook events.

The following is the implementation of the HookCallback method of the virtual MouseHook class:

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) {

p>

case MouseEvents.LeftButtonDown:

GetMousePosition(wparam, lparam, ref x, ref y);

break;

// .. .

}

MouseEvent(mEvent, new Point(x, y));

}

First, pay attention to this class definition An event MouseEvent - This class fires this event when a hook event is received. This class converts data from WPARAM and LPARAM types into meaningful mouse event data in .NET before firing its events. This frees consumers of the class from having to worry about interpreting these data structures. This class uses the imported GetMousePosition function - which we defined in the C DLL to convert these values. For this, please see the discussion in the following paragraphs.

In this method, we check if anyone is listening to this event. If not, there is no need to proceed with the incident. Then, we convert WPARAM into a MouseEvents enumeration type. We have carefully constructed the MouseEvents enumeration to exactly match their corresponding constants in C. This allows us to simply convert pointer values ??to enumeration types. Note, however, that this conversion will succeed even if the value of WPARAM does not match an enumeration value. The value of mEvent will simply be undefined (not null, just not within the enumeration value range). To do this, analyze the System.Enum.IsDefined method in detail.

Next, after determining the type of event we received, the class activates the event and notifies the consumer of the type of mouse event and the position of the mouse during the event.

Final note, regarding converting WPARAM and LPARAM values: the values ??and meanings of these variables are different for each type of event. Therefore, in each hook type, we must interpret these values ??differently. I chose to implement this conversion in C, rather than trying to imitate complex C structures and pointers in C#. For example, the previous class uses a C function called GetMousePosition.

Here is this method in a C DLL:

bool GetMousePosition(WPARAM wparam, LPARAM lparam, int amp; x, int amp; y) {

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 temporarily pass it back to the C layer to extract the value we need. Note that since we need to return some value from this call, we pass our integer as a reference variable. This maps directly to int* in C#. However, we can override this behavior by selecting the correct signature to import this method.

private static extern bool InternalGetMousePosition(UIntPtr wparam, IntPtr lparam, ref int x, ref int y)

By defining the integer parameter as ref int, we get the C reference passed to Our values. We can also use out int if we want.

5. Limitations

Some hook types are not suitable for implementing global hooks. I'm currently considering a workaround - it would allow restricted hook types to be used. For now, do not add these types back to the library, as they will cause the application to fail (often a system-wide catastrophic failure). The next section will focus on the reasons behind and solutions to these limitations.

HookTypes.CallWindowProcedure

HookTypes.CallWindowProret

HookTypes.ComputerBasedTraining

HookTypes.Debug

HookTypes.ForegroundIdle

HookTypes.JournalRecord

HookTypes.JournalPlayback

HookTypes.GetMessage

HookTypes.SystemMessageFilter

6. Two Types of hooks

In this section, I will try to explain why some hook types are restricted to certain categories while others are not. Please forgive me if I use terminology that's a bit off. I haven't found any documentation on this part of the topic, so I made up my own vocabulary. Also, if you think I'm fundamentally wrong, please tell me.

When Windows calls the callback function passed to SetWindowsHookEx(), they will be called differently for different types of hooks. There are basically two cases: hooks that switch execution context and hooks that don't switch execution context. To put it another way, that is, the case where the hook callback function is executed in the process space of the hooked application and the case where the hook callback function is executed in the process space of the hooked application.

Hook types such as mouse and keyboard hooks switch context before being called by Windows.

The entire process is roughly as follows:

1. Application X has 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 parameters in the process space of the hooked application.

5. Windows takes over the context from the hooked application and switches the execution context back to application X.

6. Windows puts the message into application X's message queue.

7. A little while later, when application X executes, it takes the message from its message queue and calls its internal key (or release or press) handler.

8. Application X continues to execute...

Hook types such as CBT hooks (window creation, etc.) do not switch contexts. For these types of hooks, the process is roughly as follows:

1. Application X has focus and executes.

2. Application X creates a window.

3. Windows calls the hook callback function with the CBT event message parameters in the application X process space.

4. Application X continues execution...

This should explain why certain types of hooks work with this library structure and some do not. Remember, this is exactly what this library does. After steps 4 and 3 above, insert the following steps:

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. The managed agent is executed with appropriate parameters.

5. The target callback function returns and executes the hook processing corresponding to the specified message.

Steps 3 and 4 are doomed to fail due to non-switching hook types. The third step will fail because the corresponding managed callback function will not be set for this application. Remember, this DLL uses global variables to track these managed agents and the hook DLL is loaded into each process space. But this value is only set in the process space of the application that places the hook. For all other cases, they are all null.

Tim Sylvester pointed out in his article "Other hook types" that using a shared memory section will solve this problem. This is true, but also as Tim pointed out, those hosted proxy addresses are meaningless to any process other than the application placing the hook. This means that they are meaningless and cannot be called during the execution of the callback function. That would cause trouble.

So, in order to use these callback functions with hook types that don't perform context switches, you need some kind of inter-process communication.

I have experimented with this idea - using an out-of-process COM object in an unmanaged DLL hook callback function for IPC. If you can make this approach work, I'd be happy to learn about it. As for my attempts, the results were not ideal. The basic reason is that it is difficult to initialize the COM unit correctly for various processes and their threads (CoInitialize(NULL)). This is a basic requirement before you can use COM objects.

I have no doubt that there must be a way to solve this problem. But I haven't tried them yet because I think they have only limited use. For example, the CBT hook lets you cancel a window creation if you wish. You can imagine what would happen to make this work.

1. The hook callback function starts executing.

2. Call the corresponding hook callback function in the unmanaged hook DLL.

3. Execution must be routed back to the main hook application.

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 action to be taken from the main hook application.

7. The hook callback function in the unmanaged hook DLL takes appropriate action in response to 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 removes the mystery surrounding the allowed and restricted hook types in the library.

7. Other library documentation: We have included relatively complete code documentation for the ManagedHooks class library. This is converted into standard help XML via Visual Studio.NET when compiling with the "Documentation" build configuration. Finally, we have used NDoc to convert it into compiled HTML help (CHM). You can view this help file by simply clicking on the Hooks.chm file in the program's Solution Explorer or by locating the downloadable ZIP file associated with the article. Enhanced IntelliSense: If you are not familiar with how Visual Studio.NET uses compiled XML files (pre-NDoc output) to enhance IntelliSense for reference library projects, let me briefly explain. If you decide to use this library in your application, you may consider copying a stable build of the library to a location where you would like to reference it. At the same time, copy the XML document file (SystemHooks\ManagedHooks\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 IntelliSense documentation. This is very useful, especially for third-party libraries like this. Unit tests: I believe that all libraries should have corresponding unit tests. This shouldn't surprise anyone since I'm a partner and software engineer at a company that does unit testing of software for .NET environments. Therefore, you will find a unit test project in the 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 testing software. In this unit test, I paid special attention to this - here, an invalid argument to a method could cause a C memory exception to occur. Even though the library is fairly simple, the unit tests did help me catch some bugs in more subtle situations. Unmanaged/managed debugging: One of the trickiest things about hybrid solutions (such as this article's managed and unmanaged code) is the debugging issue. If you want to step through the C code or set breakpoints in the C code, you must enable unmanaged debugging. This is a project setting in Visual Studio.NET. Note that you can step through the managed and unmanaged tiers very smoothly, however, unmanaged debugging does severely slow down your application's load time and execution during debugging.

8. Final warning

Obviously, system hooks are quite powerful; however, this power should be used with responsibility. When system hooks go wrong, they don't just bring down your application. They can bring down every application running on your current system. But the possibility of reaching this level is generally very small. Nonetheless, you still need to double-check your code when using system hooks.

I discovered a useful technique for developing applications - it uses system hooks to install a copy of your favorite development operating system and Visual Studio.NET on a Microsoft Virtual PC . You can then develop your application in this virtual environment. This way, when an error occurs in your hooked applications, they will only exit the virtual instance of your operating system and not your real operating system. I've had to reboot my real OS when this virtual OS crashed due to a hook bug, but that's not often.