键盘挂钩中的ToAscii / ToUnicode会破坏死键

时间:2009-12-26 23:24:27

标签: windows unicode diacritics keyboard-hook

如果在全局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);
}

8 个答案:

答案 0 :(得分:3)

  1. 停止使用ToAscii()并使用ToUncode()
  2. 请记住,ToUnicode可能会在死键上没有返回任何内容 - 这就是为什么它们被称为死键。
  3. 任何密钥都有扫描码或虚拟密钥代码,但不一定是字符。
  4. 您不应将按钮与字符组合 - 假设任何键/按钮的文本表示形式(Unicode)都是错误的。

    所以:

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

答案 1 :(得分:2)

调用'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)
 ...

答案 2 :(得分:2)

回答ToAsciiToUnicode两次就是答案。 我发现了这个并将其转换为Delphi,它确实有效!

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

答案 3 :(得分:1)

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

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

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

  

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

答案 4 :(得分:1)

我在用C#创建按键记录器时遇到了这个问题,以上答案对我都不起作用。

在深入的博客搜索之后,我偶然发现了这个keyboard listener,它可以完美地处理死键。

答案 5 :(得分:0)

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

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

这样做的好处是不会延迟os的密钥处理

答案 6 :(得分:0)

这里是完整的代码,其中包含使用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;
    }

答案 7 :(得分:0)

这对我有用

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
}