windows MapVirtualKey在MAPVK_VK_TO_CHAR模式下返回错误字符

z31licg0  于 2023-04-07  发布在  Windows
关注(0)|答案(1)|浏览(195)

我尝试使用MapVirtualKey[A]/[W]/[ExA]/[ExW] API通过MAPVK_VK_TO_CHAR (2)模式将VK_*代码Map到字符。
我发现无论我使用哪种键盘布局,它总是为'VK_A'..'VK_Z'返回'A'..'Z'字符。
The docs表示:
uCode参数是一个虚拟键代码,并被转换为返回值的低位字中未移位的字符值。通过设置返回值的顶部位来指示死键(变音符号)。如果没有转换,则函数返回0。
但是我不能从它得到unshifted character value也不是非ASCII字符。
对于其他按钮,它的工作原理如上所述。考虑到这种行为甚至更烦人,例如对于美国英语键盘布局,它返回:

VK_Q (0x51) -> `Q` (U+0051 Latin Capital Letter Q)
VK_OEM_PERIOD (0xbe) -> `.` (U+002E Full Stop)

但对于俄语键盘布局,它返回:

VK_Q (0x51) -> `Q` (U+0051 Latin Capital Letter Q)
                ^- here it should return `й` (U+0439 Cyrillic Small Letter Short I) according to docs
VK_OEM_PERIOD (0xbe) -> `ю` (U+044E Cyrillic Small Letter Yu)

如何正确使用?

gywdnpxw

gywdnpxw1#

MapVirtualKey有一个known broken behaviour

**2023年1月更新:**Microsoft docs was updated包含此行为。

The docs是骗你关于MAPVK_VK_TO_CHAR2模式。
根据实验和leaked Windows XP source code(在\windows\core\ntuser\kernel\xlate.c文件中),它包含'A'..'Z' VKs的不同行为(这些VKs在Win32 API WinUser.h头中没有明确定义,相当于'A'..'Z' ASCII字符):

case 2:

        /*
         * Bogus Win3.1 functionality: despite SDK documenation, return uppercase for
         * VK_A through VK_Z
         */
        if ((wCode >= (WORD)'A') && (wCode <= (WORD)'Z')) {
            return wCode;
        }

不知道为什么微软决定从Win 3. 1中删除这个错误,但我的Windows 10上的当前情况是这样的。
此外,some keyboard layouts可以在一次按键上发出多个WCHAR字符(UTF-16 surrogate pairs或可以包含多个Unicode代码点的连字)。MapVirtualKeyMAPVK_VK_TO_CHAR也无法为这些键返回正确的值-在这种情况下,它将返回U+F002代码点。
作为一种解决方法,我可以建议您使用ToUnicode[Ex] API,它可以为您完成此Map:

// Returns UTF-8 string
std::string GetStrFromKeyPress(uint16_t scanCode, bool isShift)
{
    static BYTE keyboardState[256];
    memset(keyboardState, 0, 256);

    if (isShift)
    {
        keyboardState[VK_SHIFT] |= 0x80;
    }

    wchar_t chars[5] = { 0 };
    const UINT vkCode = ::MapVirtualKeyW(scanCode, MAPVK_VSC_TO_VK_EX);
    // This call can produce multiple UTF-16 code points
    // in case of ligatures or non-BMP Unicode chars that have hi and low surrogate
    // See examples: https://kbdlayout.info/features/ligatures
    int code = ::ToUnicode(vkCode, scanCode, keyboardState, chars, 4, 0);

    if (code < 0)
    {
        // Dead key
        if (chars[0] == 0 || std::iswcntrl(chars[0])) {
            return {};
        }

        code = -code;
    }

    // Clear keyboard state
    {
        memset(keyboardState, 0, 256);

        const UINT clearVkCode = VK_DECIMAL;
        const UINT clearScanCode = ::MapVirtualKeyW(clearVkCode, MAPVK_VK_TO_VSC);
        wchar_t tmpChars[5] = { 0 };
        do {} while (::ToUnicode(clearVkCode, clearScanCode, keyboardState, tmpChars, 4, 0) < 0);
    }

    // Do not return control characters
    if (code <= 0 || (code == 1 && std::iswcntrl(chars[0]))) {
        return {};
    }

    return utf8::narrow(chars, code);
}

或者更好:如果您有Win32消息循环-只需使用TranslateMessage()(在后台调用ToUnicode()),然后处理WM_CHAR消息。
PS:这同样适用于GetKeyNameText API,因为它在键盘布局dll中没有显式名称设置的键的后台调用MapVirtualKey(vk, MAPVK_VK_TO_CHAR)(通常只有非字符才有名称)。

相关问题