2010-08-23 103 views
4

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

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

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

该问题在Windows 7 64位,.NET 4.0中是一个C#程序。我认为这些都不重要。

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

如果钩子方法返回简单或对键进行最小限度的处理(我会发布什么会在一秒内改变行为),键盘按照程序中的预期运行。但是,如果我从User32.dll中调用此函数ToAscii来确定我的键盘OemTilde或类似键上的哪个键,那么任何“覆盖下一个键”的键都将停止运行。我不知道这些键的正确名称,但是两个撇号类型,和', as well asand',停止运作。

例如,如果我打~然后N,它显示如下:

  • 没有键盘钩子安装:安装ñ
  • 随着键盘钩子:N(注意没有〜上文)

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

现在我会解决在其他程序中正确处理密钥的问题,即使这意味着我无法在自己的程序中正确检测到正确的密钥序列。

一些更多信息:

如果我所说的ToAscii函数作为钩方法的一部分,则发生另一个问题。诸如¨之类的键被处理两次,即,如果我点击¨一次,记事本收到两个¨¨字符,并且打N现在只是增加了N

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


我的程序可能是有点在那个特殊:

  • 我不使用键盘的状态(即。我传过来的“Key State”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); 
} 

回答

2

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

ToAscii/ToUnicode in a keyboard hook destroys dead keys.

更新:我没有看到你的代码,但处理KeyboardProc回调函数的参数时,可以检查你传递键盘消息何时code参数小于0?文档说:

代码 [IN] int

A码挂钩过程使用,以确定如何处理该消息。 如果代码小于零,则挂钩过程必须将消息传递给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); 
} 
+0

删除对'ToAscii'的调用会很好,但由于我的应用程序的全部重点是*显示*您按哪些键,并且国际键盘上的许多键返回导致不同键的虚拟键码,所以我需要*一些*获取这些密钥的方式。也许我可以缓存所有这些,尽管这可能是可能的... – 2010-08-23 15:43:32

+0

@Lasse V. Karlsen:我没有深入研究,但也许你可以通过使用[MapVirtualKey](http:// msdn.microsoft.com/en-us/library/ms646306.aspx)函数? – 2010-08-23 15:47:37

+0

或者你可以在*调用'ToAscii' /'ToUnicode'之前检查是否按下了死键? – 2010-08-23 15:55:56

1

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

我鼓励您阅读Michael Kaplan撰写的以下博客文章。它帮助我理清了一些错误。

+0

我其实并不太在乎死锁。我的程序是一个拦截击键的程序,根据配置文件和哪个应用程序处于活动状态,可以显示键的含义,以及按键顺序的可视化表示以激活该功能。我想知道是否循环遍历应用程序启动时的所有组合键,并缓存ToAscii的结果会给我想要的。我做的*想要的是像OemTilde这样的键来显示正确的字母,但我实际上并不需要死锁,我只需要避免打破它们的一般功能。 – 2010-08-23 15:45:25

2

的信息的关键位,从你的问题,你用这两个键盘钩子的缺失?简单的WH_KEYBOARD_LL无法工作。您最终会使用程序的键盘状态,而不是实际获得按键的程序。死钥匙确实有所作为。

困难的WH_KEYBOARD需要一个钩子,你不能在托管代码中写入。您需要一个可以在每个进程中注入的非托管DLL。一旦你明白了,我只是不打扰键盘挂钩,不妨用WH_CALLWNDPROC记录WM_CHAR消息。

一个示例DLL可用here

+0

我使用'_LL'版本,并且对于我来说我并不需要键盘状态。我设法使用MapVirtualKey函数来查找某个键是否是死键,然后不对其调用ToAscii,因为无论如何它对死键都没有什么用处。 – 2010-08-23 18:33:51