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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1964614/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 13:38:57  来源:igfitidea点击:

ToAscii/ToUnicode in a keyboard hook destroys dead keys

windowsunicodediacriticskeyboard-hook

提问by 00010000

It seems that if you call ToAscii()or ToUnicode()while in a global WH_KEYBOARD_LL hook, and a dead-key is pressed, it will be 'destroyed'.

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

For example, say you've configured your input language in Windows as Spanish, and you want to type an accented letter áin a program. Normally, you'd press the single-quote key (the dead key), then the letter "a", and then on the screen an accented áwould be displayed, as expected.

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

But this doesn't work if you call ToAscii()or ToUnicode()in a low-level keyboard hook function. It seems that the dead key is destroyed, and so no accented letter áshows up on screen. Removing a call to the above functions resolves the issue... but unfortunately, I need to be able to call those functions.

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

I Googled for a while, and while a lot of people seemed to have this issue, no good solution was provided.

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

Any help would be much appreciated!

任何帮助将非常感激!

EDIT:I'm calling ToAscii()to convert the virtual-key code and scan code received in my LowLevelKeyboardProchook function into the resulting character that will be displayed on screen for the user.

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

I tried MapVirtualKey(kbHookData->vkCode, 2), but this isn't as "complete" a function as ToAscii(); for example, if you press Shift + 2, you'll get '2', not '@' (or whatever Shift + 2 will produce for the user's keyboard layout/language).

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

ToAscii()is perfect... until a dead-key is pressed.

ToAscii()是完美的......直到按下一个死键。

EDIT2:Here's the hook function, with irrelevant info removed:

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);
}

回答by Kristian Kraljic

Quite an old thread. Unfortunately it didn't contain the answer I was looking for and none of the answers seemed to work properly. I finally solved the problem by checking the MSBof the MapVirtualKeyfunction, before calling ToUnicode/ ToAscii. Seems to be working like a charm:

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

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

Quoting MSDN on the return value of MapVirtualKey, if MAPVK_VK_TO_CHARis used:

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

[...] Dead keys (diacritics) are indicated by setting the top bit of the return value. [...]

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

回答by sorin

  1. stop using ToAscii() and use ToUncode()
  2. remember that ToUnicode may return you nothing on dead keys - this is why they are called dead keys.
  3. Any key will have a scancode or a virtual key code but not necessary a character.
  1. 停止使用 ToAscii() 并使用 ToUncode()
  2. 请记住,ToUnicode 可能不会在死键上返回任何内容 - 这就是它们被称为死键的原因。
  3. 任何键都有一个扫描码或虚拟键码,但不是必需的字符。

You shouldn't combine the buttons with characters - assuming that any key/button has a text representation (Unicode) is wrong.

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

So:

所以:

  • for input textuse the charactersreported by Windows
  • for checking button pressed(ex. games) use scancodesor virtual keys(probably virtual keys are better).
  • for keyboard shortcutsuse virtual keycodes.
  • 对于输入文本,使用Windows 报告的字符
  • 检查按下的按钮(例如游戏)使用扫描码虚拟键(可能虚拟键更好)。
  • 对于键盘快捷键,请使用虚拟键代码。

回答by Blue eyes

Call 'ToAscii' function twice for a correct processing of dead-key, like in:

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

回答by Martin

Calling the ToAsciior ToUnicodetwice is the answer. I found this and converted it for Delphi, and it works!

打电话ToAsciiToUnicode两次是答案。我找到了这个并将其转换为 Delphi,它有效!

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

回答by Linoy Belchich

I encountered this issue while creating a key logger in C# and none of the above answers worked for me.

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

After a deep blog searching, I stumbled across this keyboard listenerwhich handles dead keys perfectly.

在深入的博客搜索之后,我偶然发现了这个可以完美处理死键的键盘监听器

回答by Stefan Pintilie

Here is a full code which covers dead keys and shortcut keys using ALT + NUMPAD, basically a full implementation of a TextField input handling:

这是一个完整的代码,它涵盖了使用 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;
    }

回答by battmanux

I copy the vkCode in a queue and do the conversion from another thread

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

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

This has the advantage of not delaying key processing by the os

这具有不延迟操作系统的密钥处理的优点