【问题标题】:ToUnicode doesn't return correct charactersToUnicode 不返回正确的字符
【发布时间】:2018-12-09 22:33:33
【问题描述】:

我尝试在低级键盘钩子中调用ToUnicode 并打印它返回的字符。但是,该函数似乎没有考虑是否按下了特殊键,例如 shift 或 caps lock,因此输出与 MapVirtualKey 函数的输出相同,当前键的虚拟代码作为参数传递。

例如(pressed keys => characters returned by ToUnicode):

abcd => abcd (correct)
[caps lock]abcd => abcd (wrong: should be ABCD)
ab[holding shift]cd => abcd (wrong: should be abCD)

我如何调用函数(在钩子过程中):

    KBDLLHOOKSTRUCT* pressedKeyInformation = (KBDLLHOOKSTRUCT*)lParam;

    BYTE keysStates[256]; // 256 bo tyle virtualnych klawiszy wpisze GetKeyboardState

    if(!GetKeyboardState(keysStates))
        //error
    else
    {
        WCHAR charactersPressed[8] = {};

        int charactersCopiedAmount = ToUnicode(pressedKeyInformation->vkCode, pressedKeyInformation->scanCode, keysStates, charactersPressed, 8, 0);

        //std::wcout << ...
    }

后来我注意到,在ToUnicode 之前使用任何作为参数传递的虚拟键代码(例如VK_RETURNVK_SHIFT)调用GetKeyState 会导致它返回正确的字符,例如:

abcd => abcd (correct)
[caps lock]abcd => ABCD (correct)
ab[holding shift]cd => abCD (correct)

它还会正确返回与 AltGr 按下的键盘区域设置相关的键,例如[AltGr]a =&gt; ą.

上面的例子并不完全正确,因为出现了另一个问题——例如大写锁定被按下,下一个字符仍然取决于它之前的状态,只有后面的字符受到影响,例如:

abcd => abcd (correct)
(caps lock is off)[caps lock]abcd => aBCD (wrong: should be ABCD)
(caps lock is off)ab[caps lock]cd => abcD (wrong: should be abCD)

您知道为什么GetKeyState(&lt;whatever&gt;) 解决了其中一个问题以及导致后一个大写锁定(和其他特殊键)问题的原因是什么?

【问题讨论】:

  • 在调用 GetKeyboardState 之前同时调用 GetKeyState(VK_SHIFT)GetKeyState(VK_CAPITAL)。您的MCVE 不清楚。你在回复WM_KEYDOWN吗?
  • @Barmak Shemirani 是的,我正在回复WM_KEYDOWN。我会在我回家后更改代码,所以在几个小时内。
  • @BarmakShemirani 现在可以完美运行,谢谢。但是这些调用实际上做了什么?我相信GetKeyState(key) 在内部更新key 的状态,对吗?我在msdn上找不到任何线索。您应该发表您的评论作为答案。

标签: winapi keyboard hook keyboard-hook


【解决方案1】:

部分答案:

Windows 文档建议 GetKeyboardStateGetKeyState 为相应的键返回相似的结果,当这些函数用于 Windows 消息循环中时确实如此,其中键盘消息被正确翻译。

然而,在这种情况下,我们有一个钩子函数,GetKeyboardState 没有正确填充键盘。首先调用GetKeyState 将改变键盘状态,随后调用GetKeyboardState 将按预期工作。不知道为什么!

其他奇怪,GetKeyState 返回SHORT 值,而GetKeyboardState 填充BYTE 数组。但这应该没什么区别,因为我们只对高位和低位感兴趣。

HHOOK hook;
LRESULT CALLBACK hook_procedure(int code, WPARAM wparam, LPARAM lparam)
{
    if(code == HC_ACTION)
    {
        if(wparam == WM_KEYDOWN)
        {
            KBDLLHOOKSTRUCT *kb = (KBDLLHOOKSTRUCT*)lparam;
            BYTE state[256] = { 0 };
            wchar_t str[10] = { 0 };
            GetKeyState(VK_SHIFT);
            GetKeyState(VK_MENU);
            GetKeyboardState(state);
            if (ToUnicode(kb->vkCode, kb->scanCode, 
                state, str, sizeof(str)/sizeof(*str) - 1, 0) > 0)
            {
                if(kb->vkCode == VK_RETURN) std::wcout << "\r\n";
                else std::wcout << str;
            }
        }
    }
    return CallNextHookEx(hook, code, wparam, lparam);
}

【讨论】:

  • 谢谢!显示ToUnicode 返回的字符的注释 - 根据msdn:However, the buffer may contain more characters than the return value specifies. When this happens, any extra characters are invalid and should be ignored.,所以如果您只显示N 字符会很好,其中NToUnicode 返回的值。
  • @Jason,是的,这是有道理的。这也适用于AltGr 吗?
  • 如果您的意思是死键是否产生单个字符,答案是肯定的,但前提是死键可以与另一个按下的键组合 - 根据 msdn:The most common cause for this [i.e. ToUnicode buffer contains &gt;= 2 characters] is that a dead-key character (accent or diacritic) stored in the keyboard layout could not be combined with the specified virtual key to form a single character.顺便说一句,如果我们必须遵守规则,你应该 return CallNextHookEx 如果code &lt; 0,在拦截任何键之前,如钩子过程的文档中所述。
  • 另外,如果将 8 个字符复制到 str 缓冲区,则 str[8] = 0 将导致未定义的行为。但是,仍然:this buffer may be returned without being null-terminated even though the variable name suggests that it is null-terminated..
  • 好的,我在缓冲区中添加了一个额外的索引。
猜你喜欢
  • 2020-04-22
  • 2017-08-07
  • 2014-11-09
  • 2016-09-10
  • 2021-09-25
  • 1970-01-01
  • 2015-05-19
  • 1970-01-01
  • 2014-12-12
相关资源
最近更新 更多