【问题标题】:ToAscii/ToUnicode in a keyboard hook destroys dead keys键盘钩子中的 ToAscii/ToUnicode 会破坏死键
【发布时间】:2010-12-30 04:38:59
【问题描述】:

似乎如果您在全局 WH_KEYBOARD_LL 挂钩中调用 ToAscii()ToUnicode(),并且按下了死键,它将被“销毁”。

例如,假设您已将 Windows 中的输入语言配置为西班牙语,并且您想在程序中键入带重音符号的字母 á。通常,您会按单引号键(死键),然后按字母“a”,然后屏幕上会按预期显示带重音符号的 á

但是,如果您在低级键盘挂钩函数中调用 ToAscii()ToUnicode(),这将不起作用。似乎死键已被破坏,因此屏幕上没有显示重音字母 á。删除对上述函数的调用可以解决问题……但不幸的是,我需要能够调用这些函数。

我用谷歌搜索了一段时间,虽然很多人似乎有这个问题,但没有提供好的解决方案。

任何帮助将不胜感激!

编辑:我正在调用ToAscii(),将LowLevelKeyboardProc钩子函数中收到的虚拟键代码和扫描代码转换为将在屏幕上为用户显示的结果字符.

我试过MapVirtualKey(kbHookData->vkCode, 2),但这不像ToAscii()那样“完整”的功能;例如,如果您按 Shift + 2,您将得到“2”,而不是“@”(或者任何 Shift + 2 将为用户的键盘布局/语言产生的任何内容)。

ToAscii() 是完美的……直到按下死键。

EDIT2:这是钩子函数,删除了不相关的信息:

LRESULT CALLBACK keyboard_LL_hook_func(int code, WPARAM wParam, LPARAM lParam) {

    LPKBDLLHOOKSTRUCT kbHookData = (LPKBDLLHOOKSTRUCT)lParam;
    BYTE keyboard_state[256];

    if (code < 0) {
        return CallNextHookEx(keyHook, code, wParam, lParam);
    }

    WORD wCharacter = 0;

    GetKeyboardState(&keyboard_state);
    int ta = ToAscii((UINT)kbHookData->vkCode, kbHookData->scanCode,
                     keyboard_state, &wCharacter, 0);

    /* If ta == -1, a dead-key was pressed. The dead-key will be "destroyed"
     * and you'll no longer be able to create any accented characters. Remove
     * the call to ToAscii() above, and you can then create accented characters. */

    return CallNextHookEx(keyHook, code, wParam, lParam);
}

【问题讨论】:

  • 请显示调用 ToUnicode() 的代码。你从哪里得到 lpKeyState 参数?
  • @jdigital:嗯,我亲自打电话给 ToAscii,但我读过 ToUnicode 也会发生这种情况。我的电话基本上是 WH_KEYBOARD_LL 挂钩函数中的 ToAscii(kbHookData->vkCode, kbHookData->scanCode, GetKeyboardState(), &lpchar, 0) 。当 ToAscii 调用返回 -1 时,这意味着按下了一个死键......并且正如我在上面解释的那样,这个死键被“销毁”。
  • 我删除了我的答案,这是完全不正确的,因为假设您指的是 c-runtime 函数来转换字符串而不是 Win32 apis 来转换扫描码......对不起。
  • 如果您能描述这样做的目的,我会很有帮助,我们可能会为您的问题找到其他解决方案。
  • @Sorin:有关更多信息,请参阅对我的原始问题的编辑。 :-)

标签: windows unicode diacritics keyboard-hook


【解决方案1】:

这对我有用

byte[] keyState = new byte[256];

//Remove this if using
//GetKeyboardState(keyState); 

//Add only the Keys you want
keysDown[(int)Keys.ShiftKey] = 0x80; // SHIFT down
keysDown[(int)Keys.Menu] = 0x80; // ALT down
keysDown[(int)Keys.ControlKey] = 0x80; // CONTROL down
  
//ToAscii should work fine         
if (ToAscii(myKeyboardStruct.VirtualKeyCode, myKeyboardStruct.ScanCode, keyState, inBuffer, myKeyboardStruct.Flags) == 1)
{
    //do something
}

【讨论】:

    【解决方案2】:

    这是一个完整的代码,涵盖了使用 ALT + NUMPAD 的死键和快捷键,基本上是 TextField 输入处理的完整实现:​​

        [DllImport("user32.dll")]
        public static extern int ToUnicode(uint virtualKeyCode, uint scanCode, byte[] keyboardState, [Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)] StringBuilder receivingBuffer, int bufferSize, uint flags);
    
        private StringBuilder _pressCharBuffer = new StringBuilder(256);
        private byte[] _pressCharKeyboardState = new byte[256];
    
        public bool PreFilterMessage(ref Message m)
        {
            var handled = false;
    
            if (m.Msg == 0x0100 || m.Msg == 0x0102)
            {
    
                bool isShiftPressed = (ModifierKeys & Keys.Shift) != 0;
                bool isControlPressed = (ModifierKeys & Keys.Control) != 0;
                bool isAltPressed = (ModifierKeys & Keys.Alt) != 0;
                bool isAltGrPressed = (ModifierKeys & Keys.RMenu) != 0;
    
                for (int i = 0; i < 256; i++)
                    _pressCharKeyboardState[i] = 0;
    
                if (isShiftPressed)
                    _pressCharKeyboardState[(int)Keys.ShiftKey] = 0xff;
    
                if (isAltGrPressed)
                {
                    _pressCharKeyboardState[(int)Keys.ControlKey] = 0xff;
                    _pressCharKeyboardState[(int)Keys.Menu] = 0xff;
                }
    
                if (Control.IsKeyLocked(Keys.CapsLock))
                    _pressCharKeyboardState[(int)Keys.CapsLock] = 0xff;
    
                Char chr = (Char)0;
    
                int ret = ToUnicode((uint)m.WParam.ToInt32(), 0, _pressCharKeyboardState, _pressCharBuffer, 256, 0);
    
                if (ret == 0)
                    chr = Char.ConvertFromUtf32(m.WParam.ToInt32())[0];
                if (ret == -1)
                    ToUnicode((uint)m.WParam.ToInt32(), 0, _pressCharKeyboardState, _pressCharBuffer, 256, 0);
                else if (_pressCharBuffer.Length > 0)
                    chr = _pressCharBuffer[0];
    
                if (m.Msg == 0x0102 && Char.IsWhiteSpace(chr))
                    chr = (Char)0;
    
    
                if (ret >= 0 && chr > 0)
                {
    
                //DO YOUR STUFF using either "chr" as special key (UP, DOWN, etc..) 
                //either _pressCharBuffer.ToString()(can contain more than one character if dead key was pressed before)
                //and don't forget to set the "handled" to true, so nobody else can use the message afterwards
    
                }
            }
    
            return handled;
        }
    

    【讨论】:

      【解决方案3】:

      我在 C# 中创建键盘记录器时遇到了这个问题,但以上答案均不适合我。

      经过深入的博客搜索,我偶然发现了这个keyboard listener,它完美地处理了死键。

      【讨论】:

        【解决方案4】:

        相当老的线程。不幸的是,它没有包含我正在寻找的答案,而且似乎没有一个答案能正常工作。在调用ToUnicode/ToAscii之前,我终于通过检查MapVirtualKey函数的MSB解决了这个问题。似乎像魅力一样工作:

        if(!(MapVirtualKey(kbHookData->vkCode, MAPVK_VK_TO_CHAR)>>(sizeof(UINT)*8-1) & 1)) {
            ToAscii((UINT)kbHookData->vkCode, kbHookData->scanCode,
                keyboard_state, &wCharacter, 0);
        }
        

        如果使用MAPVK_VK_TO_CHAR,则在MapVirtualKey 的返回值上引用MSDN:

        [...] 死键(变音符号)通过设置返回值的最高位来指示。 [...]

        【讨论】:

        • 您能详细说明一下吗?我没有得到你的代码,如果你不输入 if() 怎么办?什么都不做?
        • 如果没有输入if,则当前按下的键为死键。根据您想要实现的目标,您可以对其做出反应,或者干脆忽略它,以防您只想听非死键按下。希望这会有所帮助。
        • 谢谢,我知道了
        • 不适用于例如使用“ALT GR”键的死字符(右 ALT,因为它会产生“CTRL + ALT”键消息)。试图在字母上添加重音的法语键盘布局示例:` = ì
        【解决方案5】:

        我将 vkCode 复制到队列中并从另一个线程进行转换

        @HOOKPROC
        def keyHookKFunc(code,wParam,lParam):
            global gkeyQueue
            gkeyQueue.append((code,wParam,kbd.vkCode))
            return windll.user32.CallNextHookEx(0,code,wParam,lParam)
        

        这样做的好处是不会延迟操作系统对密钥的处理

        【讨论】:

          【解决方案6】:

          调用ToAsciiToUnicode 两次就是答案。 我找到了这个并将其转换为 Delphi,它可以工作!

          cnt:=ToUnicode(VirtualKey, KeyStroke, KeyState, chars, 2, 0);
          cnt:=ToUnicode(VirtualKey, KeyStroke, KeyState, chars, 2, 0); //yes call it twice
          

          【讨论】:

          • 它好一点,因为我的 '^' 字符仅在第二次按键后显示,正如预期的那样,但它不起作用,因为重音不会超过字母,因为它应该产生 ' ô',但只产生 'o'
          • 其实你的答案和下面的@蓝眼睛一样:stackoverflow.com/a/3625039/2165463
          【解决方案7】:

          调用 'ToAscii' 函数两次以正确处理死键,如:

          int ta = ToAscii((UINT)kbHookData->vkCode, kbHookData->scanCode,
                           keyboard_state, &wCharacter, 0);
          int ta = ToAscii((UINT)kbHookData->vkCode, kbHookData->scanCode,
                           keyboard_state, &wCharacter, 0);
          If (ta == -1)
           ...
          

          【讨论】:

          • 这对我来说只是“杀死”了死键。例如。 'ë' 变成 'e',但按两次死键仍然会给出例如¨¨
          【解决方案8】:
          1. 停止使用 ToAscii() 并使用 ToUncode()
          2. 请记住,对于死键,ToUnicode 可能不会返回任何内容 - 这就是它们被称为死键的原因。
          3. 任何键都有扫描码或虚拟键码,但不一定是字符。

          您不应将按钮与字符组合在一起 - 假设任何键/按钮具有文本表示 (Unicode) 是错误的。

          所以:

          • 输入文本使用Windows报告的字符
          • 检查按钮按下(例如游戏)使用扫描码虚拟键(虚拟键可能更好)。
          • 键盘快捷键使用虚拟键代码。

          【讨论】:

          • 我尝试使用 ToUnicode(),但同样的事情发生了......“死键”被破坏了,所以当我按下重音键时会显示双重音,我可以不要写任何重音字母。卸载钩子会立即解决问题。不调用 ToAscii() 或 ToUnicode() 也可以立即解决问题。
          • 尝试复制键盘状态结构,以防止 Windows 更改原始结构。
          猜你喜欢
          • 2011-02-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-11-07
          • 1970-01-01
          相关资源
          最近更新 更多