来自 Windows 的低级键盘输入
Posted
技术标签:
【中文标题】来自 Windows 的低级键盘输入【英文标题】:Low level keyboard input from Windows 【发布时间】:2010-09-23 13:25:45 【问题描述】:哪些 win32 调用可用于从 Windows 服务全局检测按键事件(不仅针对 1 个窗口,我希望每次按下按键时都收到一条消息)?
【问题讨论】:
【参考方案1】:只需使用来自 user32 DLL 的本机函数 GetKeyState、GetAsyncKeyState 或 GetKeyboardState。
您可以在 msdn 网站上阅读我上面提到的每个功能:
h ttp://msdn.microsoft.com/en-us/library/windows/desktop/ms646301(v=vs.85).aspx
对于GetKeyState,以及
h ttp://msdn.microsoft.com/en-us/library/windows/desktop/ms646293(v=vs.85).aspx
对于GetAsyncKeyState,以及
h ttp://msdn.microsoft.com/en-us/library/windows/desktop/ms646299(v=vs.85).aspx
对于 GetKeyboardState。
如果你用 C# 编程,那么:
您的代码必须在任何类、结构或枚举声明中包含以下行:
using System.Runtime.InteropServices;
然后在具有您要检测键的方法的类中,定义上述三个函数之一供您选择,或者哪些有效,哪些无效。
[DllImport("user32.dll")]
static uint GetKeyState(byte virtualKeyCode);
//or
[DllImport("user32.dll")]
static uint GetAsyncKeyState(byte virtualKeyCode);
//or
[DllImport("user32.dll")]
static uint GetKeyboardState(byte[] virtualKeyCodes);
//NOTE: The length of the byte array parameter must be always 256 bytes!
如果您不喜欢 uint 作为返回类型,您也可以将其更改为 int。
如果原生函数总是返回 1 为真或 0 为假,那么你可以将它们的返回类型改为 bool,但如果你这样做,那么我认为你应该添加另一行本机函数定义上面的代码,即:
[MarshalAs(UnmanagedType.Bool)]
或您可以在本机函数定义的中间添加以下行(在 DllImport 字所在的行下方,在“extern”字所在的行上方):
[return: MarshalAs(UnmanagedType.Bool)]
Pinvoke 网站为这些功能提供了更好的定义,因此您不必使用整数来提及键盘的特定键,而是更易于理解的枚举。
以下链接指向 C# 中 GetKeyState 的定义:
h ttp://www.pinvoke.net/default.aspx/user32.getkeystate
以下链接指向C#中GetAsyncKeyState的定义
h ttp://www.pinvoke.net/default.aspx/user32.getasynckeystate
以下链接指向C#中GetKeyboardState的定义
h ttp://www.pinvoke.net/default.aspx/user32.getkeyboardstate
要检测哪个键被按下(随时随地)然后调用GetKeyState或GetAsyncKeyState函数,在单个参数中,提及您要检测的键,然后添加:& 0x8000,或 & 0x80 通话后。 如果提到的键被按住,则表达式的返回值不为零,否则为零。 根据表达式返回的整数,您可以确定按下或不按下哪个键。 请注意,如果您将代码放入 if 语句中,使用该表达式 != 0 作为进入条件(在循环中),则该 if 语句中的代码将被执行多次,实际上 很多次,当你按住那个键。如果你想让一些代码执行一次,当你按下一个键然后释放它,那么下面的代码就是一个trick,可以达到这个目的:
while (boolean exression that will return false when you want to quit this loop)
if (boolean expression that will return true when particular key is **held down**) //This boolean expression calls either GetKeyState or GetAsyncKeyState with the & 0x8000 or & 0x80 after the call, and then != 0 to check for **held down** key
while (true) //It's purpose is to wait for the same key that was held down to be released. After code execution, it will encounter the break keyword that will finish him, even though that his enter condition is true.
if (boolean expression that will return true when the **same** particular key is **NOT** held down! (NOT held down, means that at that time, that key was released). You can copy the same condition from the first if statement, and just change **!=**, which is next to 0, to **==**, or instead add brackets to the condition '(' and ')', and in left of '(' add '!').
//Put here the code that you want to execute once, when paticular key was pressed and then released.
break; //the while (true), which is the only way to get out of it
【讨论】:
【参考方案2】:您想使用 Win32 挂钩。特别是键盘钩子。
You can read more about it here
你想要的钩子类型是 WH_KEYBOARD,你可以通过 Win32 API SetWindowsHookEx 设置它。
基本上,每次在任何应用程序系统范围内按下一个键时,windows 都会调用您创建的 dll 中的一个函数。
钩子会调用你的函数,它会有这个接口:
LRESULT CALLBACK KeyboardProc(
int code,
WPARAM wParam,
LPARAM lParam
);
More information about this callback here.
使用 windows 挂钩,您不仅可以跨所有进程跟踪系统范围的事件,还可以过滤它们并完全停止它们。
【讨论】:
【参考方案3】:如果您在 .net 应用程序中实现此功能,我建议您使用 GlobalKeyboardHook,否则我会查看它的源代码并获取您需要的内容,因为它正在实现上述 DLLImport 函数。
【讨论】:
【参考方案4】:查看 MSDN 中的原始输入 API:
http://msdn.microsoft.com/en-us/library/ms645536(VS.85).aspx
它允许您从键盘(除其他外)获取输入,而无需使用全局挂钩。全局挂钩只能作为最后的手段使用,因为它们可能会带来意想不到的后果。
使用原始输入的唯一缺点是它可能无法正确接收由软件生成的击键。
【讨论】:
【参考方案5】:看看你可以用 SetWindowHookEx 设置的钩子:
http://msdn.microsoft.com/en-us/library/ms644990(VS.85).aspx
但是,除非您正在运行“交互式”服务,否则您将无法访问桌面(并且您不应该运行交互式服务,因为它无法在较新版本的 Windows 中正常工作)。您必须查看会话和桌面管理 API 才能访问桌面/控制台。
【讨论】:
以上是关于来自 Windows 的低级键盘输入的主要内容,如果未能解决你的问题,请参考以下文章
如何在 UITextField 子类中拦截来自键盘的文本输入事件