MapVirtualKey 出错

分享于2022年07月17日 c winapi 问答
【问题标题】:MapVirtualKey 出错(MapVirtualKey goes wrong)
【发布时间】:2022-07-08 18:11:04
【问题描述】:

我制作了一个程序,它可以捕捉每一次击键并在终端中打印出来。问题是它都是大写的,我不知道如何正确地做到这一点。如果有帮助,我可以发布代码。

主要:

#include 
#include 
#include 
#include 
#pragma comment( lib, "user32" )

int main()
{
    fun();

    printf("Loading library\n"); 
    HMODULE libHandle = LoadLibraryA("TestLibrary");
    if (libHandle == NULL) printf("***ERROR*** loading library\n");
     
    printf("Getting address of hook procedure\n");
    HOOKPROC procAddress = (HOOKPROC)GetProcAddress(libHandle, "KeyboardProc");
    if (procAddress == NULL) printf("***ERROR*** getting address\n");

    printf("Installing hook\n");
    HHOOK hook = SetWindowsHookEx(WH_KEYBOARD_LL, procAddress, libHandle, 0);
    if (hook == NULL) printf("***ERROR*** installing hook\n");

    printf("Entering message loop\n");
    while (GetMessage(NULL, NULL, 0, 0));
}

DLL:

#include "pch.h"
#include 
#include 
#include "TestLibrary.h"

void fun()
{
    printf("Program started\n");
}

LRESULT CALLBACK KeyboardProc(_In_ int code, _In_ WPARAM wParam, _In_ LPARAM lParam)
{
    if (wParam == WM_KEYDOWN)
    {
        PKBDLLHOOKSTRUCT tmp = (PKBDLLHOOKSTRUCT)lParam;
        char c = MapVirtualKeyA(tmp->vkCode, 2);

        if (tmp->vkCode == VK_RETURN)
            printf("\n");
        else if (tmp->vkCode == VK_BACK)
            printf("\b \b");
        else
            printf("%c", c);
    }
    return CallNextHookEx(NULL, code, wParam, lParam);

  • 我正在编辑我的问题
  • @AndreasWenzel 我发布了代码。谢谢!
  • 请准确说明输入、期望行为和实际行为。
  • 旁注:如果您在对 MapVirtualKeyA 的函数调用中编写 MAPVK_VK_TO_CHAR 而不是简单的 2 ,您的代码将更具可读性。
  • 低级钩子程序不需要编译成DLL。不过,它确实必须遵循 documented 协议。具体来说,除非 nCode HC_ACTION ,否则它不得进行任何处理。这不会改变您眼前的问题,但可以避免您在未来遇到问题。

【解决方案1】:

MapVirtualKey[A]/[W]/[ExA]/[ExW] 有一个 known broken behaviour

The docs MAPVK_VK_TO_CHAR 2 模式上对你撒谎。据说:

uCode 参数是一个虚拟键码,被翻译成一个 返回值的低位字中未移位的字符值。 死键(变音符号)通过设置 返回值。如果没有翻译,函数返回0。

但根据实验和 leaked Windows XP source code (在 \windows\core\ntuser\kernel\xlate.c 文件中),它包含'A'..'Z' VK 的不同行为(具体未在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;
        }

对于其他按钮,它按描述工作。考虑到这种行为更加令人讨厌,例如对于美国英语键盘布局,它返回:

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)

不知道为什么 MS 决定从 Win 3.1 中移除这个错误,但我的 Windows 10 上的当前情况是这样的。

作为一种解决方法,我建议您使用可以为您执行此映射的 ToUnicode[Ex] API:

wchar_t VkToChar(uint16_t vk, bool isShift = false)
{
    uint16_t sc = MapVirtualKeyW(vk, MAPVK_VK_TO_VSC);
    const uint32_t flags = 1 << 2; // Do not change keyboard state of this thread

    static uint8_t state[256] = { 0 };
    state[VK_SHIFT] = isShift << 7; // Modifiers set the high-order bit when pressed

    wchar_t unicodeChar;
    if (ToUnicode(vk, sc, state, &unicodeChar, 1, flags) != 1)
        return L'\0';

    if (!std::iswprint(unicodeChar))
        return L'\0';

    return unicodeChar;
}

甚至更好:如果您有 Win32 消息循环 - 只需使用 TranslateMessage() (在其代码中调用 ToUnicode() )然后处理 WM_CHAR 消息。