Low-level Windows API hooks from C# to stop unwanted keystrokes
2008-10-25 11:23
344 查看
原文:http://www.codeproject.com/KB/system/CSLLKeyboard.aspx
Download source files - 3.37 Kb
Download demo project (VS 2005) - 19.7 Kb
Download demo project (VS 2003) - 12.5 KB
The solution is an application which you can switch to as soon as the keyboard is under threat, and which will ensure that any keyboard activity is harmless. This article illustrates how the keyboard can be neutralized in a C# application using a low-level Windows API hook.
The .NET framework gives managed access to the keyboard events you'll need for most ordinary uses through
The solution is to catch the keyboard events at the operating system level rather than through the framework. To do this, the application needs to use Windows API functions to add itself to the "hook chain" of applications listening for keyboard messages from the operating system. When it receives this type of message, the application can selectively pass the message on, as it normally should, or suppress it so that no further applications -- including Windows -- can act on it. This article explains how.
Please note, however, that this code only applies to NT-based versions of Windows (NT, 2000 and XP), and that it isn't possible to use this method to disable Ctrl+Alt+Delete (suggestions on how to do that can be found in this MSDN Magazine Q&A[^]).
The class raises an event to warn the application that a key has been pressed, so it is important for the main form to have access to the instance of
Enabling Alt+Tab and/or the Windows keys allows the person who is actually using the computer to switch to another application and interact with it using the mouse while keystrokes continue to be trapped by the keyboard hook. The
The simplest way to instantiate the class to trap all keystrokes is therefore:
The
A method with the appropriate signature can then be used to perform whatever tasks the keystroke calls for. Here is an example from the enclosed sample application:
The rest of this article explains how the low-level keyboard hook is implemented in
The key to creating an application which can hijack the keyboard is to implement the first two methods, and forgo the third. The result is that any keys pressed go no further than the application.
In order to achieve this, the first step is to include the
The code to import
The next step is to call
idHook: This number determines the type of hook to be set up. For example,
lpfn: A long pointer to the function that will handle the keyboard events. In C#, the "pointer" is achieved by passing an instance of a delegate type, referring to the appropriate method. This is the method that will be called every time the hook is used.
An important point to note here is that the delegate instance needs to be stored in a member variable in the class. This is to prevent it being garbage collected as soon as the first method call ends.
hMod: An instance handle for the application which is setting up the hook. Most of the samples I found simply set this to
dwThreadId: The ID of the current thread. Setting this to 0 makes the hook global, which is the appropriate setting for a low-level keyboard hook.
The C# method for setting up a "pointer to a function" is to use a delegate, so the first step in giving
And then write a callback method with the same signature; this method will contain all the code that actually processes the keyboard event. In the case of
A reference to
Whenever a keyboard event occurs, the following parameters will be passed to
nCode: According to the MSDN documentation[^], the callback function should return the result of
wParam: This value indicates what type of event occurred: key up or key down, and whether the key pressed was a system key (left or right-hand Alt keys).
lParam: A structure to store precise information on the keystroke, such as the code of the key which was pressed. The structure declared in
The two public parameters are the only ones used by the callback method in
If the information provided by
In this case,
However, the key functionality of the
On the other hand, it does call
The imported method is then called by this line in the
As mentioned before, if the flags in
Since
A good C# console application sample can be found in Stephen Toub's MSDN blog[^]
For information on Windows API hooks in general, see MSDN's article on SetWindowsHookEx[^] and its related links
A Code Project article[^] on a class for global hook in C#, which illustrates how to set up the other available hooks, including for mouse activity.
It seems that articles in any language seem to attract at least one response on the lines of "how do I do the same thing in (some other language)?", so here are some samples I found:
A keyboard hook sample in VB6[^]
A sample and article on the same thing in VB.NET[^]
And again in C++[^] as part of a project with exactly the same aim as this one!
And nothing to do with Windows API hooks or C#, but I would like to recommend Mike Ellison's excellent Word 2003 template if you're ever planning to write an article for Code Project!
Changed
to
Many thanks to "Scottie Numbnuts" for the solution.
1.3 (13 Feb 07) - Improved modifier key support:
Added CheckModifiers() method
Deleted LoWord/HiWord methods as they weren't necessary
Implemented Barry Dorman's suggestion to AND GetKeyState values with 0x8000 to get their result
1.2 (10 Jul 06) - Added support for modifier keys:
Changed filter in HookCallback to WM_KEYUP instead of WM_KEYDOWN
Imported GetKeyState from user32.dll
Moved native DLL imports to a separate internal class as this is a Good Idea according to Microsoft's guidelines
1.1 (18 Jun 06) - Modified proc assignment in constructor to make class backward compatible with 2003.
1.0 (17 Jun 06) - First version published on Code Project.
Download source files - 3.37 Kb
Download demo project (VS 2005) - 19.7 Kb
Download demo project (VS 2003) - 12.5 KB
Introduction
Cats and babies have a lot in common. They both like eating the house plants, and share the same hatred of closed doors. They also love using keyboards, with the result that the important email you were sending to your boss is dispatched in mid-sentence, your accounts in Excel are embellished with four rows of gobbledygook, and your failure to notice that Windows Explorer was open results in several files moving to the Recycle Bin.The solution is an application which you can switch to as soon as the keyboard is under threat, and which will ensure that any keyboard activity is harmless. This article illustrates how the keyboard can be neutralized in a C# application using a low-level Windows API hook.
Background
There are a number of articles and code samples regarding hooks in Windows, and some of them are listed at the end of this article. Neutralizing the keyboard when children are around must be a common need -- someone here wrote almost exactly the same thing in C++[^]! However, when I was looking for source code to create my application, I found very few .NET examples, and none that involved a self-contained class in C#.The .NET framework gives managed access to the keyboard events you'll need for most ordinary uses through
KeyPress,
KeyUpand
KeyDown. Unfortunately, these events can't be used to stop Windows from processing key combinations like Alt+Tab or the Windows Start key, which allow users to navigate away from an application. The conveniently placed Windows key in particular was irresistible to my baby son!
The solution is to catch the keyboard events at the operating system level rather than through the framework. To do this, the application needs to use Windows API functions to add itself to the "hook chain" of applications listening for keyboard messages from the operating system. When it receives this type of message, the application can selectively pass the message on, as it normally should, or suppress it so that no further applications -- including Windows -- can act on it. This article explains how.
Please note, however, that this code only applies to NT-based versions of Windows (NT, 2000 and XP), and that it isn't possible to use this method to disable Ctrl+Alt+Delete (suggestions on how to do that can be found in this MSDN Magazine Q&A[^]).
Using the code
For ease of use, I have attached three separate zip files to this article. One contains only theKeyboardHookclass which is the main focus of this article. The others are complete projects for an application called "Baby Keyboard Bash" which displays the keys' names or coloured shapes in response to keystrokes. For ease of use, I have included two projects, one for Microsoft Visual C# 2005 Express Edition and one for Visual Studio 2003.
Instantiating the class
The keyboard hook is set up and handled by theKeyboardHookclass in keyboard.cs. This class implements
IDisposable,so the simplest way to instantiate it is to use the
usingkeyword in the application's
Main()method, to enclose the
Application.Run()call. This will ensure that the hook is set up as soon as the application starts and, more importantly, is disabled as the application shuts down.
The class raises an event to warn the application that a key has been pressed, so it is important for the main form to have access to the instance of
KeyboardHookcreated in the
Main()method; the simplest solution is to store this instance in a public member variable. In a Visual Studio 2003 project, this will usually go in the
Form1class (or whatever the application's main class is called), and in Visual Studio 2005, in the
Programclass in Program.cs.
KeyboardHookhas three constructors to enable or disable certain settings:
KeyboardHook(): traps all keystrokes, passing nothing to Windows or other applications.
KeyboardHook(string param): Converts the string to one of the values in the
Parametersenum and then calls the following constructor:
KeyboardHook(KeyboardHook.Parameters enum): Depending on the value selected from the
Parametersenum, the following settings can be enabled:
Parameters.AllowAltTab: allows the user to switch to other applications using Alt+Tab.
Parameters.AllowWindowsKey: allows the user to access the taskbar and Start menu using Ctrl+Esc or one of the Windows keys.
Parameters.AllowAltTabAndWindows: enables Alt+Tab, Ctrl+Esc and the Windows keys.
Parameters.PassAllKeysToNextApp: if the parameter is true, all keystrokes will be passed to any other listening applications, including Windows.
Enabling Alt+Tab and/or the Windows keys allows the person who is actually using the computer to switch to another application and interact with it using the mouse while keystrokes continue to be trapped by the keyboard hook. The
PassAllKeysToNextAppsetting effectively disables the keystroke trapping; the class will still set up a low-level keyboard hook and raise its
KeyInterceptedevent, but it will also pass the keyboard events to other listening applications.
The simplest way to instantiate the class to trap all keystrokes is therefore:
public staticKeyboardHook kh; [STAThread] static void Main() { //Other code using (kh = newKeyboardHook()) { Application.Run(new Form1()); }
Handling the KeyIntercepted event
When a key is pressed, theKeyboardHookclass raises a
KeyInterceptedevent containing some
KeyboardHookEventArgs. This needs to be handled by a method of the type
KeyboardHookEventHandler, which can be set up as follows:
kh.KeyIntercepted += newKeyboardHook.KeyboardHookEventHandler(kh_KeyIntercepted);
The
KeyboardHookEventArgsreturns the following information on the key that was pressed:
KeyName: the key's name, obtained by casting the trapped key code to
System.Windows.Forms.Keys.
KeyCode: the original key code returned by the keyboard hook
PassThrough: whether this instance of
KeyboardHookis configured to allow this keystroke through to other applications. Checking this is useful if you want to allow a user to switch to another application using Alt+Tab or Ctrl+Esc/Windows keys.
A method with the appropriate signature can then be used to perform whatever tasks the keystroke calls for. Here is an example from the enclosed sample application:
void kh_KeyIntercepted(KeyboardHookEventArgs e) { //Check if this key event is being passed to //other applications and disable TopMost in //case they need to come to the front if (e.PassThrough) { this.TopMost = false; } ds.Draw(e.KeyName); }
The rest of this article explains how the low-level keyboard hook is implemented in
KeyboardHook.
Implementing a low-level Windows API keyboard hook
The Windows API contains three methods in user32.dll that are useful for this purpose:SetWindowsHookEx, which sets up the keyboard hook
UnhookWindowsHookEx, which removes the keyboard hook
CallNextHookEx, which passes the keystroke information to the next application listening for keyboard events
The key to creating an application which can hijack the keyboard is to implement the first two methods, and forgo the third. The result is that any keys pressed go no further than the application.
In order to achieve this, the first step is to include the
System.Runtime.InteropServicesnamespace and import the API methods, starting with
SetWindowsHookEx:
using System.Runtime.InteropServices ... //Inside class: [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
The code to import
UnhookWindowsHookExand
CallNextHookExis listed in the sections concerning those methods further in this article.
The next step is to call
SetWindowsHookExto set up the hook, passing the following four parameters:
idHook: This number determines the type of hook to be set up. For example,
SetWindowsHookExcan also be used to hook into mouse events; a complete list can be found on MSDN[^]. In this case, we're only interested in number 13, which is the keyboard hook's id. To make the code more legible, this has been assigned to the constant
WH_KEYBOARD_LL.
lpfn: A long pointer to the function that will handle the keyboard events. In C#, the "pointer" is achieved by passing an instance of a delegate type, referring to the appropriate method. This is the method that will be called every time the hook is used.
An important point to note here is that the delegate instance needs to be stored in a member variable in the class. This is to prevent it being garbage collected as soon as the first method call ends.
hMod: An instance handle for the application which is setting up the hook. Most of the samples I found simply set this to
IntPtr.Zero, on the grounds that it is unlikely that there will be more than one instance of the application. However, this code uses
GetModuleHandlefrom kernel32.dll to identify the exact instance to make the class potentially more flexible.
dwThreadId: The ID of the current thread. Setting this to 0 makes the hook global, which is the appropriate setting for a low-level keyboard hook.
SetWindowsHookExreturns a hook ID which will be used to unhook the application when it shuts down, so this needs to be stored in a member variable for future use. The relevant code from the
KeyboardHookclass is as follows:
private HookHandlerDelegate proc; private IntPtr hookID = IntPtr.Zero; private const int WH_KEYBOARD_LL = 13; publicKeyboardHook() { proc = new HookHandlerDelegate(HookCallback); using (Process curProcess = Process.GetCurrentProcess()) using (ProcessModule curModule = curProcess.MainModule) { hookID = SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0); } }
Processing keyboard events
As mentioned above,SetWindowsHookExrequires a pointer to the callback function that will be used to process the keyboard events. It expects a function with the following signature:
LRESULT CALLBACK LowLevelKeyboardProc ( int nCode, WPARAM wParam, LPARAM lParam );
The C# method for setting up a "pointer to a function" is to use a delegate, so the first step in giving
SetWindowsHookExwhat it needs is to declare a delegate with the right signature:
private delegate IntPtr HookHandlerDelegate( int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam);
And then write a callback method with the same signature; this method will contain all the code that actually processes the keyboard event. In the case of
KeyboardHook, it checks whether the keystroke should be passed to other applications and then raises the
KeyInterceptedevent. Here is a simplified version without the keystroke handling code:
private const int WM_KEYUP = 0x0101; private const int WM_SYSKEYUP = 0x0105; private IntPtr HookCallback(int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam) { //Filter wParam for KeyUp events only - otherwise this code //will execute twice for each keystroke (ie: on KeyDown and KeyUp) //WM_SYSKEYUP is necessary to trap Alt-key combinations if (nCode >= 0) { if (wParam == (IntPtr)WM_KEYUP || wParam == (IntPtr)WM_SYSKEYUP) { //Raise the event OnKeyIntercepted(newKeyboardHookEventArgs(lParam.vkCode, AllowKey)); } //Return a dummy value to trap the keystroke return (System.IntPtr)1; } //The event wasn't handled, pass it to next application return CallNextHookEx(hookID, nCode, wParam, ref lParam); }
A reference to
HookCallbackis then assigned to an instance of
HookHandlerDelegateand passed in the call to
SetWindowsHookEx, as illustrated in the previous section.
Whenever a keyboard event occurs, the following parameters will be passed to
HookCallBack:
nCode: According to the MSDN documentation[^], the callback function should return the result of
CallNextHookExif this value is less than zero. Normal keyboard events will return an
nCodeof 0 or more.
wParam: This value indicates what type of event occurred: key up or key down, and whether the key pressed was a system key (left or right-hand Alt keys).
lParam: A structure to store precise information on the keystroke, such as the code of the key which was pressed. The structure declared in
KeyboardHookis as follows:
private struct KBDLLHOOKSTRUCT { public int vkCode; int scanCode; public int flags; int time; int dwExtraInfo; }
The two public parameters are the only ones used by the callback method in
KeyboardHook.
vkCokereturns the virtual key code, which can be cast to
System.Windows.Forms.Keysto obtain the key's name, while
flagsindicates if this is an extended key (the Windows Start key, for instance) or if the Alt key was pressed at the same time. The complete code for the
HookCallbackmethod illustrates which
flagsvalues to check for in each case.
If the information provided by
flagsand the other components of the
KBDLLHOOKSTRUCTare not needed, the signature of the callback method and delegate can be changed as follows:
private delegate IntPtr HookHandlerDelegate( int nCode, IntPtr wParam, IntPtr lParam);
In this case,
lParamwill return only the
vkCode.
Passing keystrokes to the next application
A well-behaved keyboard hook callback method should end by calling theCallNextHookExfunction and returning its result. This ensures that other applications get a chance to handle the keystrokes destined for them.
However, the key functionality of the
KeyboardHookclass is preventing keystrokes from being propagated to any further applications. So whenever it processes a keystroke,
HookCallbackreturns a dummy value instead:
return (System.IntPtr)1;
On the other hand, it does call
CallNextHookExif it didn't handle the event, or if the parameter passed with
KeyboardHook's overloaded constructor allows certain key combinations through.
CallNextHookExis enabled by importing the function from user32.dll as shown in the following code:
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, ref KeyInfoStruct lParam);
The imported method is then called by this line in the
HookCallbackmethod, which ensures that all the parameters received through the hook are passed on to the next application:
CallNextHookEx(hookID, nCode, wParam, ref lParam);
As mentioned before, if the flags in
lParamare not relevant, the signature for the imported
CallNextHookExcan be changed to define
lParamas
System.IntPtr.
Removing the hook
The last step in processing the hook is to remove it when the instance of theKeyboardHookclass is destroyed, using the
UnhookWindowsHookExfunction imported from user32.dll as follows.
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool UnhookWindowsHookEx(IntPtr hhk);
Since
KeyboardHookimplements
IDisposable, this can be done in the
Disposemethod.
public void Dispose() { UnhookWindowsHookEx(hookID); }
hookIDis the id returned by the call to
SetWindowsHookExin the constructor. This removes the application from the hook chain.
Sources
Here are some good sources on Windows hooks in C# and in general:A good C# console application sample can be found in Stephen Toub's MSDN blog[^]
For information on Windows API hooks in general, see MSDN's article on SetWindowsHookEx[^] and its related links
A Code Project article[^] on a class for global hook in C#, which illustrates how to set up the other available hooks, including for mouse activity.
It seems that articles in any language seem to attract at least one response on the lines of "how do I do the same thing in (some other language)?", so here are some samples I found:
A keyboard hook sample in VB6[^]
A sample and article on the same thing in VB.NET[^]
And again in C++[^] as part of a project with exactly the same aim as this one!
And nothing to do with Windows API hooks or C#, but I would like to recommend Mike Ellison's excellent Word 2003 template if you're ever planning to write an article for Code Project!
History
1.4 (23 Mar 07) - Fixed a bug which made Alt key appear stuck when the program exited:Changed
if (nCode >= 0 && (wParam == (IntPtr)WM_KEYUP || wParam == (IntPtr)WM_SYSKEYUP))
to
if (nCode >= 0) { if (wParam == (IntPtr)WM_KEYUP || wParam == (IntPtr)WM_SYSKEYUP)
Many thanks to "Scottie Numbnuts" for the solution.
1.3 (13 Feb 07) - Improved modifier key support:
Added CheckModifiers() method
Deleted LoWord/HiWord methods as they weren't necessary
Implemented Barry Dorman's suggestion to AND GetKeyState values with 0x8000 to get their result
1.2 (10 Jul 06) - Added support for modifier keys:
Changed filter in HookCallback to WM_KEYUP instead of WM_KEYDOWN
Imported GetKeyState from user32.dll
Moved native DLL imports to a separate internal class as this is a Good Idea according to Microsoft's guidelines
1.1 (18 Jun 06) - Modified proc assignment in constructor to make class backward compatible with 2003.
1.0 (17 Jun 06) - First version published on Code Project.
License
相关文章推荐
- Class To Change Wallpaper In Xp,Vista,Windows 7 From C#.Net
- Call to Action: Current plan for Windows Vista Aero will degrade OpenGL performance. Act Now to keep OpenGL a first class API un
- How to launch Windows applications (Notepad) / Call Batch files from a C#/.NET console application.
- Beginning C# 2008 Databases: From Novice to Professional
- Beginning ASP.NET 3.5 in C# 2008: From Novice to Professional, Second Edition
- Calling a Web API From a .NET Client (C#)
- WindowsNT Buffer Overflow's From Start to Finish
- How To Remote Desktop from windows to Linux
- How to change the language of oracle sqlplus from chinese to english in windows(如何将oracle sql plus中的中文变成英文版)
- 精彩控件件源码(3)-ImageListPopup, a C# class which pops up a window to select an image from an image list
- How to use C# code to get the windows components
- 解决React Native unable to load script from assets index.android.bundle on windows
- HOW TO: Set a Windows Hook in Visual C# .NET
- How to Uninstall AVG Programs from A Windows System
- Beginning C# Objects From Concepts to Code
- 利用low-level API构建的几种DBus工作流程(转)
- Beginning C# 2005 Databases: From Novice to Professional
- C#+Windows API操纵系统菜单
- How to make Windows Form app truly Full Screen (and to hide Taskbar) in C#? 转
- 直接插入 - An Insecure JSON Data Transference from C# Server Page to Smart Devices Case Share