【问题标题】:GetKeyboardState one key delayGetKeyboardState 一键延迟
【发布时间】:2017-05-10 12:34:54
【问题描述】:

我正在处理ToUnicodeEx 函数,它需要键盘状态作为输入参数。所以,我使用了GetKeyboardState 函数来做到这一点。但我注意到,当我使用 SHIFT+A 等修饰键键入组合键时,会出现一个字符延迟。这是一个例子。

aaa(现在按住 SHIFT)aAAAAAAA(释放 SHIFT)Aaaa

在调试此问题时,我注意到 GetKeyboardState 导致了此延迟。我该如何处理或防止这种延迟?

这是我的整个键盘钩子过程。

void proc(KBDLLHOOKSTRUCT kbdStruct) {


    fdebug = fopen("debug.txt", "a");
    foutput= fopen("output.txt", "a");
    WCHAR pwszBuff[9];
    WCHAR key[9];
    char str[8];
    BOOL isDead = FALSE;
    BYTE lpKeyState[256];
    HWND currentHwnd = GetForegroundWindow();
    LPDWORD currentProcessID = 0;
    DWORD currentWindowThreadID = GetWindowThreadProcessId(currentHwnd, currentProcessID);
    DWORD thisProgramThreadId = GetCurrentThreadId();
    hkl = GetKeyboardLayout(thisProgramThreadId);
    if (AttachThreadInput(thisProgramThreadId, currentWindowThreadID, TRUE))
    {
        GetKeyboardState(lpKeyState);

        AttachThreadInput(thisProgramThreadId, currentWindowThreadID, FALSE);
    }
    else
    {
        GetKeyboardState(lpKeyState);
    }

    int ret = ToUnicodeEx(kbdStruct.vkCode, kbdStruct.scanCode, lpKeyState, pwszBuff, 8, 0, hkl);
    fprintf(fdebug, "vkCode: %d\n", (int)kbdStruct.vkCode);
    fprintf(fdebug, "ret: %d\n", (int)ret);
    fprintf(fdebug, "lastIsDead: %d\n", (int)lastIsDead);
    fprintf(fdebug, "lastIsMod: %d\n", (int)lastIsMod);
    fprintf(fdebug, "lastVKCode: %d\n", (int)lastVKCode);
    if (ret == -1) {
        isDead = TRUE;
        ClearKeyboardBuffer(kbdStruct.vkCode, kbdStruct.scanCode, hkl);
    }
    else if (ret == 0) {

    }
    else {
        memcpy(&key, &pwszBuff, sizeof(pwszBuff));
        WideCharToMultiByte(CP_UTF8, 0, key, -1, str, sizeof(str), NULL, NULL);
        fprintf(fdebug, "str: %s\n", str);
    }

    if (lastVKCode != 0 && lastIsDead == TRUE) {
        ToUnicodeEx(lastVKCode, lastScanCode, lastKeyState, pwszBuff, 4, 0, hkl);
        memcpy(&key, &pwszBuff, sizeof(pwszBuff));
        WideCharToMultiByte(CP_UTF8, 0, key, -1, str, sizeof(str), NULL, NULL);
        fprintf(fdebug, "str: %s\n", str);
        lastVKCode = 0;

    }
    fprintf(fdebug, "%s", "---------------------------------------------------\n");

    fprintf(foutput, "LSHIFT: %d\n", (int)lpKeyState[160]);
    fprintf(foutput, "RSHIFT: %d\n", (int)lpKeyState[161]);
    fprintf(foutput, "%s", "---------------------------------------------------\n\n");

    lastVKCode = kbdStruct.vkCode;
    lastScanCode = kbdStruct.scanCode;
    lastIsDead = isDead;
    fclose(fdebug);
    fclose(foutput);
}

这里是 hookcallback 的更新版本,感谢 Ton Plooij。但是,我还是有同样的问题。

LRESULT __stdcall HookCallback(int nCode, WPARAM wParam, LPARAM lParam)
{
    LRESULT ret = CallNextHookEx(_hook, nCode, wParam, lParam);
    if (nCode >= 0)
    {
        if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)
        {
            hookStruct = *((KBDLLHOOKSTRUCT*)lParam);
            proc(hookStruct);

        }
    }
    return ret;
}

【问题讨论】:

  • 键盘状态在低级键盘钩子返回后更新。我不知道在低级键盘钩子中查询同步键盘状态的方法。
  • 有什么办法可以解决这个问题吗?我尝试了 GetAsyncKetSatate 函数,但它仍然需要钩子返回来更新缓冲的键状态。

标签: c++ winapi keyboard-hook


【解决方案1】:
AttachThreadInput(thisProgramThreadId, currentWindowThreadID, FALSE);

这并没有达到您希望的效果。有时。当您调用 GetKeyboardState() 时,获得正确的值是一项勇敢且必要的努力。我花了一段时间才找到故障模式,一点也不明显,我无法让代码以同样的方式失败。当 GUI 进程处于前台时它工作得很好,例如使用记事本或 VS 尝试。

不是,当它是控制台模式进程时。

解释有点复杂,实际上是 GetWindowThreadProcessId() 返回了误导信息。它试图保持拥有控制台窗口的是控制台进程的错觉。它没有,它实际上是拥有它的关联 conhost.exe 进程。在旧的 Windows 版本上是 csrss.exe,在 Windows 7 中添加了 conhost.exe 来解决 UIPI(又名 UAC)导致的拖放问题。

但没有任何体面的方法来发现拥有该窗口的特定 conhost.exe 进程,更不用说线程 ID。 this Q+A的主题。很怀疑它是否会对您有所帮助。

只有体面的建议是众所周知的不愉快的建议:如果您需要可靠地翻译击键,那么您需要使用 WH_KEYBOARD 挂钩而不是 WH_KEYBOARD_LL。所以 GetKeyboardState() 总是准确的,钩子回调在进程中运行。

【讨论】:

  • 我用一个简单的 GUI 进程尝试了这段代码,但当它不在前台时仍然无法正常工作。老实说,我不明白 GetWindowThreadProcessId 函数有什么问题。这是一个全局钩子,为什么返回值受线程 ID 影响?最后我不想让我的程序 dll 依赖所以我不能使用 WH_KEYBOARD。感谢您的精彩回答。
【解决方案2】:

LowLevelKeyboardProc 在其 wParam 中接收 WM_KEYUP 和 WM_KEYDOWN 消息。您可以简单地自己跟踪按下的修饰键,在这种情况下检测向下和向上移动。将键状态信息存储在静态变量中,并使用它来测试在处理其他键时是否按下了 shift 而不是使用 GetKeyState。

【讨论】:

  • 我试过这个,但我认为 ToUnicodeEx 函数需要 lpKeyState 参数的 255 个键状态。所以只有 shift 或 alt 键对我来说是不够的。 msdn.microsoft.com/en-us/library/windows/desktop/…
  • 好的,然后处理完代码并在下次调用时使用返回的信息后使用 GetKeyboardState?
  • 我修改了我的钩子回调并添加到问题中,但仍然有同样的问题。现在我先调用下一个钩子,然后处理代码但同样的事情。我对你说的话有误吗?
  • 不错的方法,但我的意思有所不同(但我确实有点简洁)。我的建议是使用原始代码,但不要在需要之前调用 GetKeyboardState,而是在需要之后调用它(在 close(foutput) 之后)并将结果存储在静态缓冲区中。在下次调用时使用此缓冲区。
  • 但是 GetKeyboardState 已经落后了一个字符,所以如果我这样做,结果将变为 aaa(现在持有 shift)aaAAAAAA(release shift)AAaa 对吗?也许我应该将除修饰键之外的按键存储在静态缓冲区中,并在当前 GetKeyboardState 的下一次调用中使用。
【解决方案3】:

您可以尝试 GetAsyncKeyState()。 我的键盘钩子模块中的这个功能完美地工作。

https://msdn.microsoft.com/en-us/library/windows/desktop/ms646293(v=vs.85).aspx

这个函数好像可以在按键的时候捕获硬件中断。

您的 GetKeyboardState 与 Windows 注册表有关。 好像跟下面的有关系:

“HKEY_USERS\DEFAULT\ControlPanel\Keyboard”,修改“KeyboardDelay”值为0(默认1) 并将“KeyboardSpeed”值更改为 48(默认 31)。

【讨论】:

  • “HKEY_USERS\DEFAULT 是一个 UAC 保护区,但我在 HKCU 中找到了相同的密钥,并且按照您所说的更改了值,并且它正在工作。我可以在程序加载时使用这些值并以编程方式对其进行更改。接受这个答案:)
  • 我不知道 Windows 出了什么问题,但是当我刚刚修改值时它起作用了,现在它不起作用了。我检查了注册表值仍然是 0 和 48。
  • 这很奇怪。我无法解释这种现象的原因。对不起。
【解决方案4】:

根据 Hans Passant 的回答,我搜索了如何从 GetWindowThreadProcessId() 获得正确的值,并成功地而不是每次在钩子回调中获取 hWndwindowThreadID,我在程序之后将这些值获取到全局变量在钩子回调中启动和使用变量。

【讨论】:

  • 这似乎无法回答问题。也许您打算接受汉斯的回答。
  • @DavidHeffernan,Hans 的回答解释了您需要做什么才能使其正常工作,但暗示这可能是不可能的。这个答案解释了这是可能的,至少在 OPs 的特定场景中,以及他是如何做到的。如果不是一个非常明确的答案,绝对是一种尝试。
猜你喜欢
  • 2012-07-18
  • 1970-01-01
  • 1970-01-01
  • 2014-02-14
  • 1970-01-01
  • 1970-01-01
  • 2012-12-27
  • 2020-11-03
  • 1970-01-01
相关资源
最近更新 更多