【问题标题】:Keyboard hook changes the behavior of keys键盘钩子改变了按键的行为
【发布时间】:2010-08-23 15:15:24
【问题描述】:

我正在创建一个程序,它安装一个键盘钩子来捕获所有键并显示一些与它们相关的文本。

但是,我遇到了一个问题,那就是安装钩子时一些键会改变行为。

我将看到发布一个小而完整的测试程序,但现在我只描述问题。

该问题在 .NET 4.0 中的 Windows 7 64 位 C# 程序中出现。我认为这些都不重要。

我的钩子通过SetWindowsHookEx 自行安装,然后处理系统中处理的所有密钥。

如果钩子方法只是简单地返回,或者只对键进行最少的处理(我将在一秒钟内发布改变行为的内容),那么键盘将按程序中的预期工作。

但是,如果我从 User32.dll 调用此函数 ToAscii 来确定我的键盘 OemTilde 或类似键上的哪个键,那么任何“覆盖下一个键”的键都将停止运行。我不知道这些键的正确名称,但两种撇号类型,` 和 ´, as well as~and¨` 停止工作。

例如,如果我点击~,然后点击N,它会显示如下:

  • 未安装键盘挂钩:ñ
  • 安装了键盘挂钩:n(注意没有 ~ 上面)

有谁知道为什么会发生这种情况以及如何解决这个问题?

现在我将满足于在其他程序中正确处理按键,即使这意味着我将无法在我自己的程序中正确检测到正确的按键序列。

更多信息:

如果我将ToAscii 函数作为钩子方法的一部分调用,则会出现不同的问题。像¨ 这样的键被处理了两次,即。如果我点击一次¨,记事本会收到两个¨¨ 字符,现在点击N 只需添加N

但是,如果我使用BeginInvoke在单独的线程上处理键,从键盘钩子方法返回后,出现第一个问题。


我的程序可能有点特别:

  • 我不使用键盘状态(即我传递的“键状态”256 字节数组只是充满了 0)
  • 我不关心死键(从某种意义上说,我的程序不会处理它们,我只关心它们,我不希望我的程序使它们对系统的其余部分无用)

因此,我的代码最终如下所示:

private bool IsDeadKey(uint key)
{
    return ((Hook.Interop.MapVirtualKey(key, 2) & 2147483648) == 2147483648);
}

void _Hook_KeyDown_Async(KeyDownEventArgs e)
{
    var inBuffer = new byte[2];
    char key = '\0';
    if (!IsDeadKey((uint)e.KeyCode))
    {
        int ascii = Hook.Interop.ToAscii((int) e.KeyCode,
                                            e.ScanCode,
                                            _KeyState,
                                            inBuffer,
                                            e.Flags);
        if (ascii == 1)
        {
            key = Char.ToUpper((char) inBuffer[0]);
        }
    }

    BeginInvoke(
        new Action<Keys, Boolean, Boolean, Boolean, Char>(ProcessKeyboardEvent),
        e.KeyCode, e.Control, e.Shift, e.Alt, key);
}

【问题讨论】:

    标签: .net windows keyboard-hook


    【解决方案1】:

    这些键称为dead keys,您可以通过删除对ToAscii 的调用来解决问题。另请参阅以下相关主题:

    ToAscii/ToUnicode in a keyboard hook destroys dead keys.

    更新:我没有看到你的代码,但是在处理KeyboardProc回调函数的参数时,你能检查一下你在code参数小于0时传递了键盘消息吗?文档说:

    代码 [in] int

    钩子程序用来确定如何处理消息的代码。 如果code小于零,钩子程序必须将消息传递给CallNextHookEx函数而不做进一步处理,并应返回CallNextHookEx返回的值。

    MSDN 中有setting up a managed hook 的示例:

    if (nCode < 0)
    {
        return CallNextHookEx(hHook, nCode, wParam, lParam);
    }
    else
    {
        // process message here
    
        return CallNextHookEx(hHook, nCode, wParam, lParam); 
    }
    

    【讨论】:

    • 删除对ToAscii 的调用会很好,但是由于我的应用程序的全部目的是显示您按下了哪些键,并且国际键盘上的许多键返回虚拟导致不同键的键码,我需要 some 方法来获取这些键。也许我可以缓存所有这些,这可能是可能的......
    • @Lasse V. Karlsen:我没有深入研究这个,但也许你可以使用MapVirtualKey函数来获取角色?
    • 或者您是否能够检查是否在调用ToAscii/ToUnicode之前按下了死键?
    • 我正在使用 MapVirtualKey,谢谢,这正是我所需要的。由于我不使用键盘状态,而且我也不关心死键,只是我只关心不想破坏它们在系统其余部分中的含义,检测到它是死键在调用 ToAscii 之前确实是解决方案。尽管这里的所有答案都给了我有用的建议,但我会接受你的回答。
    【解决方案2】:

    您的问题缺少关键信息,您使用两个键盘挂钩中的哪一个?最简单的 WH_KEYBOARD_LL 不能工作。您最终将使用程序的键盘状态,而不是实际获得击键的程序。死键确实有所作为。

    最难的,WH_KEYBOARD 需要一个您无法在托管代码中编写的挂钩。您需要一个可以在每个进程中注入的非托管 DLL。一旦你明白了,我就不用担心键盘钩子了,还不如用 WH_CALLWNDPROC 记录 WM_CHAR 消息。

    一个示例 DLL 可用here

    【讨论】:

    • 我使用的是_LL 版本,对于我的使用,我实际上并不需要键盘状态。我设法使用 MapVirtualKey 函数来查找键是否为死键,然后不对其调用 ToAscii,因为无论如何它对于我使用的死键并没有真正的用处。
    【解决方案3】:

    您可能在这里看到的是在涉及死键时尝试映射键的效果。键盘映射是一个相当复杂的过程,在产生这种行为的某些类型的键周围存在很多缺陷。

    我鼓励您阅读 Michael Kaplan 关于该主题的以下博客系列。它帮助我解决了一些错误。

    【讨论】:

    • 我实际上并不太关心死键。我的程序是一个拦截击键的程序,并且可以根据配置文件和哪个应用程序处于活动状态,显示键的含义,以及击键序列的可视化表示以激活该功能。我想知道是否只是在应用程序启动时循环所有组合键并缓存 ToAscii 的结果会给我想要的东西。我想要的是像OemTilde 这样的键来显示正确的字母,但我实际上并不需要死键,我只需要避免破坏它们的一般功能。
    【解决方案4】:

    鉴于当前的答案和对国际行为的参考,您可能需要考虑“代码页”。代码页因国家/地区而异。

    国家代码页示例

    • 美国、英国 437
    • 多语言 850
    • 斯拉夫语 852
    • 葡萄牙语860
    • 冰岛语 861
    • 加拿大人,法国人 863
    • 斯堪的纳维亚/北欧 865

    More Info

    MSDN Info

    Internationalization for Windows Applications

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-10-08
      • 2011-01-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多