今天有一个需求,要写一个DEMO,在WIN10下监听LWin+F20、LWin+F19、LWin+F18.三个快捷键。
之前写了个DEMO,是通过修改注册表的方式,修改Iink工作区的启动对象,这种方法缺点是用户可以自己通过ink工作区修改启动对象,其次在执行修改注册表时需要管理员权限。
老规矩,先贴查询过的资料
HID API 相关
http://blog.csdn.net/u010875635/article/details/73321066
RawInput 相关
https://msdn.microsoft.com/en-us/library/windows/desktop/ms645546(v=vs.85).aspx
RegisterHotKey百度去搜,连接太多了。被审核了。去掉了这个
Virtual-KeyCodes 键值码相关 连接太多,呗审核了,去掉这个
今天首先使用RawInput实现了监听,但是发现需要和设备建立连接后,通过 Win32.GetRawInputData,从连接的设备中获取数据包;而后找到自己要的内容,通过事件返回给UI。RawInput在使用时主要遇到的问题是找到那个设备是你要建立连接的。而后做数据包的拆分,在拆分数据包时对数据包每一位是干啥的不知道。所以比如在LWin+F20,只抓到了返回结果Keys为91.而F20键值为131.所以发数据包的时候只抓到了LWin的包。没抓到F20的包,当时很纠结问题在哪。因为当时没考虑到是组合键。就以为自己解析数据包时不对。而后放弃了RawInput的写法。(后面找到了问题。)
同时也去查找了一下RegisterHotKey注册热键失败的问题到底是什么。
RegisterHotKey在MSDN的备注说明上给了以下几个解释
This function cannot associate a hot keywith a window created by another thread.
RegisterHotKey fails if the keystrokesspecified for the hot key have already been registered by another hot key.
If a hot key already exists with thesame hWnd and id parameters, it is maintained along with the new hot key. Theapplication must explicitly callUnregisterHotKeyto unregister the old hot key.
The F12 key isreserved for use by the debugger at all times, so it should not be registeredas a hot key. Even when you are not debugging an application, F12 is reservedin case a kernel-mode debugger or a just-in-time debugger is resident.
这也就是为什么我通过RegisterHotKey注册LWin+F20和LWin+F19LWin+F18失败的原因。
今天主要说的是Hook。因为在遇到RawInput问题时,为了快速的完成任务。我改用了之前比较熟悉的Hook。
先解释一下什么是Hook,解释之前先看一下Windows的说明:
Windows平台是基于事件驱动机制的,整个系统都是通过消息的传递来实现的。当进程有响应时(包括响应鼠标和键盘事件),则Windows会向应用程序发送一个消息给应用程序的消息队列,应用程序进而从消息队列中取出消息并发送给相应窗口进行处理。
而Hook则是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。
所以Hook就可以实现在键盘/鼠标响应后,窗口处理消息之前,就对此消息进行处理,比如监听键盘输入,鼠标点击坐标等等。某些盗号木马就是Hook了指定的进程,从而监听键盘输入了什么内容,进而盗取账户密码。
这里解释的很清楚当进程有响应时windows会想应用程序发送一个消息给应用程序的消息队列。Hook是在键盘/鼠标响应后,窗体处理消息之前,对消息进行处理,hook本身的机制允许应用程序截获处理windows消息或特定的事件。也就是说,让你截获这个事件之后,你可以选择让他继续下发到对应的程序上,也可以选择消费掉这个消息而不发送到对应的程序上;
废话不多说,直接上代码。首先是一个单独的KeyboardHook辅助类,需要注意的是KeyboardHookProc事件,里面我只监听了键盘按下状态时的131、130、129.这些键值码怎么来的,去博文开头Virtual-KeyCodes键值码相关的连接里找对应的值
classKeyboardHook
{
public eventSystem.Windows.Forms.KeyEventHandler KeyDownEvent;
public delegate voidKeyBoardEventHandle(object sender, Keys e);
public event KeyBoardEventHandleKeyBoardEvent;
public event KeyPressEventHandlerKeyPressEvent;
public eventSystem.Windows.Forms.KeyEventHandler KeyUpEvent;
public delegate int HookProc(intnCode, Int32 wParam, IntPtr lParam);
static int hKeyboardHook = 0;
public const int WH_KEYBOARD_LL = 13; //线程键盘钩子监听鼠标消息设为2,全局键盘监听鼠标消息设为13
HookProc KeyboardHookProcedure; //声明KeyboardHookProcedure作为HookProc类型
//键盘结构
[StructLayout(LayoutKind.Sequential)]
public class KeyboardHookStruct
{
public intvkCode; //定一个虚拟键码。该代码必须有一个价值的范围1至254
public intscanCode; // 指定的硬件扫描码的关键
public intflags; // 键标志
public inttime; // 指定的时间戳记的这个讯息
public intdwExtraInfo; // 指定额外信息相关的信息
}
//安装钩子
[DllImport("user32.dll",CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern intSetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
//卸载钩子
[DllImport("user32.dll",CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern boolUnhookWindowsHookEx(int idHook);
//继续下一个钩子
[DllImport("user32.dll",CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern intCallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);
// 取得当前线程编号
[DllImport("kernel32.dll")]
static extern intGetCurrentThreadId();
//使用WINDOWS API函数代替获取当前实例的函数,防止钩子失效
[DllImport("kernel32.dll")]
public static extern IntPtrGetModuleHandle(string name);
public void Start()
{
// 键盘钩子
if(hKeyboardHook == 0)
{
KeyboardHookProcedure = new HookProc(KeyboardHookProc);
hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure,GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName),0);
if (hKeyboardHook == 0)
{
Stop();
throw new Exception("安装键盘钩子失败");
}
}
}
public void Stop()
{
boolretKeyboard = true;
if(hKeyboardHook != 0)
{
retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
hKeyboardHook = 0;
}
if (!(retKeyboard)) throw new Exception("卸载钩子失败!");
}
[DllImport("user32")]
public static extern int ToAscii(intuVirtKey, //[in] 指定虚拟关键代码进行翻译。
int uScanCode, // [in] 指定的硬件扫描码的关键须翻译成英文。高阶位的这个值设定的关键,如果是(不压)
byte[] lpbKeyState, // [in] 指针,以256字节数组,包含当前键盘的状态。每个元素(字节)的数组包含状态的一个关键。如果高阶位的字节是一套,关键是下跌(按下)。在低比特,如果设置表明,关键是对切换。在此功能,只有肘位的CAPS LOCK键是相关的。在切换状态的NUM个锁和滚动锁定键被忽略。
byte[] lpwTransKey, // [out] 指针的缓冲区收到翻译字符或字符。
int fuState); // [in] Specifies whether a menu is active. This parameter mustbe 1 if a menu is active, or 0 otherwise.
//获取按键的状态
[DllImport("user32")]
public static extern intGetKeyboardState(byte[] pbKeyState);
[DllImport("user32.dll",CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern shortGetKeyState(int vKey);
private const int WM_KEYDOWN = 0x100;//KEYDOWN
private const int WM_KEYUP =0x101;//KEYUP
private const int WM_SYSKEYDOWN =0x104;//SYSKEYDOWN
private const int WM_SYSKEYUP =0x105;//SYSKEYUP
protected virtual int HookCallbackProcedure(int nCode, Int32 wParam, IntPtrlParam)
{
return 0;
}
int[] vkCodes = newint[]{129,130,131};
private int KeyboardHookProc(intnCode, Int32 wParam, IntPtr lParam)
{
// 侦听键盘事件
if (nCode >=0&& wParam == WM_KEYDOWN)
{
KeyboardHookStruct MyKeyboardHookStruct =(KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
Keys key = (Keys)MyKeyboardHookStruct.scanCode;
for (int i = 0; i < vkCodes.Length; i++)
{
if (MyKeyboardHookStruct.vkCode ==vkCodes[i])
{
KeyBoardEvent(this, keyData);
}
}
}
//如果返回1,则结束消息,这个消息到此为止,不再传递。
//如果返回0或调用CallNextHookEx函数则消息出了这个钩子继续往下传递,也就是传给消息真正的接受者
returnCallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}
~KeyboardHook()
{
Stop();
}
}
在找到自己需要监听的键值时通过事件KeyBoardEvent(this,keyData);传递给调用者。
我只做了3个键值的监听,如果你们有其他需求,可以二次封装上面的类。下面是调用方法。
Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct))要重点说一下,
KeyboardHookStruct结构体参数Flags是一个八位的二进制,各个位的标识信息如下:
0位:扩展键的标识位。1表示该键是扩展键;0表示不是。
1位~3位:保留位,一般是0。
4位:标识消息的类型,1表示该消息是模拟的;0表示该消息是真实的
5位:Alt键的标识位。1表示Alt是按下的;0表示Alt键没有被按下
6位:保留位,一般是0
7位:按键的状态标识位。1表示按键是弹起的,0表示按键是按下的
.
在Flags参数中,第四位是能区分消息的类别。真实按键的时候,这位是0;
.
当我们用keybd_event函数模拟按键消息时,这位是1。
KeyboardHook k_hook;
privatevoid Window_Loaded(object sender, RoutedEventArgs e)
{
k_hook = newKeyboardHook();
k_hook.KeyBoardEvent +=K_hook_KeyBoardEvent; ;
k_hook.Start();
}
privatevoidK_hook_KeyBoardEvent(object sender,Keys e)
{
richTextBox1.Text += e;
}
richTextBox1是一个Textbox控件,前台界面是
<Grid>
<TextBox Name="richTextBox1" IsEnabled="False" ></TextBox>
</Grid>
Demo下载链接
https://download.csdn.net/download/input0715/10301053