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

发布于 2024-08-16 19:58:45 字数 1670 浏览 9 评论 0原文

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

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

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

我google了一下,虽然很多人似乎都有这个问题,但没有提供好的解决方案。

任何帮助将不胜感激!

编辑:我正在调用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);
}

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'.

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.

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.

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 LowLevelKeyboardProc hook function into the resulting character that will be displayed on screen for the user.

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).

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

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

碍人泪离人颜 2024-08-23 19:58:45

已知 ToUnicode() 及其旧版本的 ToAscii() 可以更改当前线程的键盘状态,从而扰乱死键和 ALT+NUMPAD 击键:

当 ToUnicodeEx 转换虚拟键代码时,它也会更改
内核模式键盘缓冲区的状态。这种状态变化会影响
死键、连字、alt+小键盘输入等。也可能是
如果与以下药物一起使用会引起不良副作用
TranslateMessage(这也会改变内核模式的状态)
键盘缓冲区)。

为了避免这种情况,您可以在单独的线程中执行 ToUnicode() 调用(它将具有单独的键盘状态)或在 wFlags 参数中使用特殊标志,该标志记录在ToUnicode() 文档

如果设置了位 2,则键盘状态不会更改(Windows 10 版本
1607 及更新版本)

或者您可以预先准备 sc->char 映射表并在 WM_INPUTLANGCHANGE 语言更改事件上更新它。

我认为它也应该与 ToAscii() 一起使用,但最好不要使用这种旧的 ANSI 代码页相关方法。使用 ToUnicode() API 代替,它甚至可以返回连字UTF-16 代理对 - 如果键盘布局有它们。 有些是

请参阅异步输入与同步输入的快速介绍
这背后的原因。

It is known that ToUnicode() and its older counterpart ToAscii() can change keyboard state of the current thread and thus mess with dead keys and ALT+NUMPAD keystrokes:

As ToUnicodeEx translates the virtual-key code, it also changes the
state of the kernel-mode keyboard buffer. This state-change affects
dead keys, ligatures, alt+numpad key entry, and so on. It might also
cause undesired side-effects if used in conjunction with
TranslateMessage (which also changes the state of the kernel-mode
keyboard buffer).

To avoid that you can do your ToUnicode() call in a separate thread (it will have a separate keyboard state) or use a special flag in wFlags param that is documented in ToUnicode() docs:

If bit 2 is set, keyboard state is not changed (Windows 10, version
1607 and newer)

Or you can prepare sc->char mapping table beforehand and update it on WM_INPUTLANGCHANGE language change event.

I think it should work with ToAscii() too but better not use this old ANSI codepage-dependant method. Use ToUnicode() API instead that can even return ligatures and UTF-16 surrogate pairs - if keyboard layout have them. Some do.

See Asynchronous input vs synchronous input, a quick introduction
for the reason behind this.

萌吟 2024-08-23 19:58:45

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

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

引用 MSDN 关于 MapVirtualKey 的返回值,如果使用 MAPVK_VK_TO_CHAR

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

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 MSB of the MapVirtualKey function, before calling ToUnicode / ToAscii. Seems to be working like a charm:

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_CHAR is used:

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

瑾兮 2024-08-23 19:58:45
  1. 停止使用 ToAscii() 并使用 ToUncode()
  2. 请记住,ToUnicode 可能不会在死键上返回任何内容 - 这就是它们被称为死键的原因。
  3. 任何键都将具有扫描码或虚拟键码,但不一定是字符。

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

因此:

  • 对于输入文本,请使用Windows报告的字符
  • 来检查按下的按钮(例如游戏),使用扫描代码虚拟键(可能虚拟键更好)。
  • 对于键盘快捷键,请使用虚拟键代码。
  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.

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

So:

  • for input text use the characters reported by Windows
  • for checking button pressed (ex. games) use scancodes or virtual keys (probably virtual keys are better).
  • for keyboard shortcuts use virtual key codes.
梦回梦里 2024-08-23 19:58:45

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

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

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

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

大姐,你呐 2024-08-23 19:58:45

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

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

    [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;
    }
心碎的声音 2024-08-23 19:58:45

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

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

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)
 ...
壹場煙雨 2024-08-23 19:58:45

调用 ToAsciiToUnicode 两次就是答案。
我找到了这个并将其转换为 Delphi,并且它有效!

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

Calling the ToAscii or ToUnicode twice is the answer.
I found this and converted it for Delphi, and it works!

cnt:=ToUnicode(VirtualKey, KeyStroke, KeyState, chars, 2, 0);
cnt:=ToUnicode(VirtualKey, KeyStroke, KeyState, chars, 2, 0); //yes call it twice
<逆流佳人身旁 2024-08-23 19:58:45

在引入 wFlag 参数的位 0x04 之前,进行非破坏性 ToUnicode 调用 是一个棘手的话题。引用的文本详细讨论了它(以及当前状态)。 (有时我怀疑可能正是这种讨论导致了 0x04 的实现......但它也被建议较早。)

引用部分的末尾有一个非常简短的“旧方法”摘要:使用 wFlags=0x01|0x02 并自行检测/实现按数字输入。 (与 按数字输入的详细信息。)

Before introduction of the bit 0x04 for the wFlag argument, doing non-destructive ToUnicode calls was a tricky topic. The referenced text discusses it (and the current state) in a lot of details. (Sometimes I suspect that it may have been this discussion which led to implementation of 0x04… But it was also suggested earlier.)

A very short summary “of the old way” is at the end of the referenced section: use wFlags=0x01|0x02 and detect/implement input-by-number yourself. (Compare with the 4th note in details of input-by-number.)

拥有 2024-08-23 19:58:45

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

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

这具有不延迟操作系统关键处理的优点

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

@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

拿命拼未来 2024-08-23 19:58:45

这对我有用

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
}

This works for me

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
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文