C#全局键盘监听(Hook)的使用(转载)

一.为什么需要全局键盘监听?

在某些情况下应用程序需要实现快捷键执行特定功能,例如大家熟知的QQ截图功能Ctrl+Alt+A快捷键,只要QQ程序在运行(无论是拥有焦点还是处于后台运行状态),都可以按下快捷键使用此功能…

这个时候在程序中添加键盘监听肯定不能满足需求了,当用户焦点不在App上时(如最小化,或者用户在处理其它事物等等)键盘监听就失效了

二.怎样才能实现全局键盘监听?

这里需要用到Windows API,源码如下:(可以作为一个工具类[KeyboardHook.cs]收藏起来)

  1 using System;
  2 using System.Runtime.InteropServices;
  3 using System.Windows.Forms;
  4 //using System.Windows.Input;
  5 
  6 namespace WpfApplication1
  7 {
  8     /// <summary>
  9     /// 键盘钩子
 10     /// [以下代码来自某网友,并非本人原创]
 11     /// </summary>
 12     class KeyboardHook
 13     {
 14         public event System.Windows.Forms.KeyEventHandler KeyDownEvent;
 15         public event KeyPressEventHandler KeyPressEvent;
 16         public event System.Windows.Forms.KeyEventHandler KeyUpEvent;
 17 
 18         public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
 19         static int hKeyboardHook = 0; //声明键盘钩子处理的初始值
 20         //值在Microsoft SDK的Winuser.h里查询
 21         // http://www.bianceng.cn/Programming/csharp/201410/45484.htm
 22         public const int WH_KEYBOARD_LL = 13;   //线程键盘钩子监听鼠标消息设为2,全局键盘监听鼠标消息设为13
 23         HookProc KeyboardHookProcedure; //声明KeyboardHookProcedure作为HookProc类型
 24         //键盘结构
 25         [StructLayout(LayoutKind.Sequential)]
 26         public class KeyboardHookStruct
 27         {
 28             public int vkCode;  //定一个虚拟键码。该代码必须有一个价值的范围1至254
 29             public int scanCode; // 指定的硬件扫描码的关键
 30             public int flags;  // 键标志
 31             public int time; // 指定的时间戳记的这个讯息
 32             public int dwExtraInfo; // 指定额外信息相关的信息
 33         }
 34         //使用此功能,安装了一个钩子
 35         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
 36         public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
 37 
 38 
 39         //调用此函数卸载钩子
 40         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
 41         public static extern bool UnhookWindowsHookEx(int idHook);
 42 
 43 
 44         //使用此功能,通过信息钩子继续下一个钩子
 45         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
 46         public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);
 47 
 48         // 取得当前线程编号(线程钩子需要用到)
 49         [DllImport("kernel32.dll")]
 50         static extern int GetCurrentThreadId();
 51 
 52         //使用WINDOWS API函数代替获取当前实例的函数,防止钩子失效
 53         [DllImport("kernel32.dll")]
 54         public static extern IntPtr GetModuleHandle(string name);
 55 
 56         public void Start()
 57         {
 58             // 安装键盘钩子
 59             if (hKeyboardHook == 0)
 60             {
 61                 KeyboardHookProcedure = new HookProc(KeyboardHookProc);
 62                 hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName), 0);
 63                 //hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);
 64                 //************************************
 65                 //键盘线程钩子
 66                 //SetWindowsHookEx( 2,KeyboardHookProcedure, IntPtr.Zero, GetCurrentThreadId());//指定要监听的线程idGetCurrentThreadId(),
 67                 //键盘全局钩子,需要引用空间(using System.Reflection;)
 68                 //SetWindowsHookEx( 13,MouseHookProcedure,Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]),0);
 69                 //
 70                 //关于SetWindowsHookEx (int idHook, HookProc lpfn, IntPtr hInstance, int threadId)函数将钩子加入到钩子链表中,说明一下四个参数:
 71                 //idHook 钩子类型,即确定钩子监听何种消息,上面的代码中设为2,即监听键盘消息并且是线程钩子,如果是全局钩子监听键盘消息应设为13,
 72                 //线程钩子监听鼠标消息设为7,全局钩子监听鼠标消息设为14。lpfn 钩子子程的地址指针。如果dwThreadId参数为0 或是一个由别的进程创建的
 73                 //线程的标识,lpfn必须指向DLL中的钩子子程。 除此以外,lpfn可以指向当前进程的一段钩子子程代码。钩子函数的入口地址,当钩子钩到任何
 74                 //消息后便调用这个函数。hInstance应用程序实例的句柄。标识包含lpfn所指的子程的DLL。如果threadId 标识当前进程创建的一个线程,而且子
 75                 //程代码位于当前进程,hInstance必须为NULL。可以很简单的设定其为本应用程序的实例句柄。threaded 与安装的钩子子程相关联的线程的标识符
 76                 //如果为0,钩子子程与所有的线程关联,即为全局钩子
 77                 //************************************
 78                 //如果SetWindowsHookEx失败
 79                 if (hKeyboardHook == 0)
 80                 {
 81                     Stop();
 82                     throw new Exception("安装键盘钩子失败");
 83                 }
 84             }
 85         }
 86         public void Stop()
 87         {
 88             bool retKeyboard = true;
 89 
 90 
 91             if (hKeyboardHook != 0)
 92             {
 93                 retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
 94                 hKeyboardHook = 0;
 95             }
 96 
 97             if (!(retKeyboard)) throw new Exception("卸载钩子失败!");
 98         }
 99         //ToAscii职能的转换指定的虚拟键码和键盘状态的相应字符或字符
100         [DllImport("user32")]
101         public static extern int ToAscii(int uVirtKey, //[in] 指定虚拟关键代码进行翻译。
102                                          int uScanCode, // [in] 指定的硬件扫描码的关键须翻译成英文。高阶位的这个值设定的关键,如果是(不压)
103                                          byte[] lpbKeyState, // [in] 指针,以256字节数组,包含当前键盘的状态。每个元素(字节)的数组包含状态的一个关键。如果高阶位的字节是一套,关键是下跌(按下)。在低比特,如果设置表明,关键是对切换。在此功能,只有肘位的CAPS LOCK键是相关的。在切换状态的NUM个锁和滚动锁定键被忽略。
104                                          byte[] lpwTransKey, // [out] 指针的缓冲区收到翻译字符或字符。
105                                          int fuState); // [in] Specifies whether a menu is active. This parameter must be 1 if a menu is active, or 0 otherwise.
106 
107         //获取按键的状态
108         [DllImport("user32")]
109         public static extern int GetKeyboardState(byte[] pbKeyState);
110 
111 
112         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
113         private static extern short GetKeyState(int vKey);
114 
115         private const int WM_KEYDOWN = 0x100;//KEYDOWN
116         private const int WM_KEYUP = 0x101;//KEYUP
117         private const int WM_SYSKEYDOWN = 0x104;//SYSKEYDOWN
118         private const int WM_SYSKEYUP = 0x105;//SYSKEYUP
119 
120         private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
121         {
122             // 侦听键盘事件
123             if ((nCode >= 0) && (KeyDownEvent != null || KeyUpEvent != null || KeyPressEvent != null))
124             {
125                 KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
126                 // raise KeyDown
127                 if (KeyDownEvent != null && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN))
128                 {
129                     Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
130                     System.Windows.Forms.KeyEventArgs e = new System.Windows.Forms.KeyEventArgs(keyData);
131                     KeyDownEvent(this, e);
132                 }
133 
134                 //键盘按下
135                 if (KeyPressEvent != null && wParam == WM_KEYDOWN)
136                 {
137                     byte[] keyState = new byte[256];
138                     GetKeyboardState(keyState);
139 
140                     byte[] inBuffer = new byte[2];
141                     if (ToAscii(MyKeyboardHookStruct.vkCode, MyKeyboardHookStruct.scanCode, keyState, inBuffer, MyKeyboardHookStruct.flags) == 1)
142                     {
143                         KeyPressEventArgs e = new KeyPressEventArgs((char)inBuffer[0]);
144                         KeyPressEvent(this, e);
145                     }
146                 }
147 
148                 // 键盘抬起
149                 if (KeyUpEvent != null && (wParam == WM_KEYUP || wParam == WM_SYSKEYUP))
150                 {
151                     Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
152                     System.Windows.Forms.KeyEventArgs e = new System.Windows.Forms.KeyEventArgs(keyData);
153                     KeyUpEvent(this, e);
154                 }
155 
156             }
157             //如果返回1,则结束消息,这个消息到此为止,不再传递。
158             //如果返回0或调用CallNextHookEx函数则消息出了这个钩子继续往下传递,也就是传给消息真正的接受者
159             return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
160         }
161         ~KeyboardHook()
162         {
163             Stop();
164         }
165     }
166 }
View Code

三.如何使用上面的工具类?

 1     public partial class hook : Window
 2     {
 3         KeyboardHook k_hook;
 4         public hook()
 5         {
 6             InitializeComponent();
 7             k_hook = new KeyboardHook();
 8             //k_hook.KeyDownEvent += new System.Windows.Forms.KeyEventHandler(hook_KeyDown);//钩住键按下 
 9             k_hook.KeyPressEvent += K_hook_KeyPressEvent;
10             k_hook.Start();//安装键盘钩子
11         }
12 
13         private void K_hook_KeyPressEvent(object sender, KeyPressEventArgs e)
14         {
15             //tb1.Text += e.KeyChar;
16             int i = (int)e.KeyChar;
17             System.Windows.Forms.MessageBox.Show(i.ToString());
18         }
19 
20         private void hook_KeyDown(object sender, KeyEventArgs e)
21         {
22             tb1.Text += (char)e.KeyData;
23 
24 
25             //判断按下的键(Alt + A) 
26             //if (e.KeyValue == (int)Keys.A && (int)System.Windows.Forms.Control.ModifierKeys == (int)Keys.Alt)
27             //{
28             //    System.Windows.Forms.MessageBox.Show("ddd");
29             //}
30         }
31 
32         private void Window_Unloaded(object sender, RoutedEventArgs e)
33         {
34             k_hook.Stop();
35         }
36     }
View Code

//注意几种不同的键值判断: 
//1>.单普通键(例如A) 
//2>.单控制键+单普通键(例如Ctrl+A) 
//3>.多控制键+单普通键(例如Ctrl+Alt+A) 
//上面的代码中演示了2,其它情况以此类推,无非就是添几个条件再&&起来就好

四.使用全局键盘监听需要注意的问题(请读者朋友务必看看)

1.在应用程序中使用全局键盘监听,会被360发现,弹窗提示用户“有程序正在监听键盘输入,是否阻止?”

所以如果程序中必须要用Hook应该告诉用户不会泄露其信息等等

或者直接把App提交给360审核

否则杀软的提示会对用户体验造成极大的影响

转载于:https://www.cnblogs.com/zhaoyuncai/p/7684958.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值