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

问题描述 投票:0回答:9

似乎如果您在全局 WH_KEYBOARD_LL 挂钩中调用

ToAscii()
ToUnicode()
并按下死键,它将被“销毁”。

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

但是,如果您在低级键盘挂钩函数中调用

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

我用Google搜索了一段时间,虽然很多人似乎都有这个问题,但没有提供好的解决方案。

任何帮助将不胜感激!

编辑:我正在调用

ToAscii()
将我的LowLevelKeyboardProc挂钩函数中收到的虚拟键代码和扫描代码转换为将在屏幕上为用户显示的结果字符。

我尝试了

MapVirtualKey(kbHookData->vkCode, 2)
,但这并不像
ToAscii()
那样“完整”;例如,如果您按 Shift + 2,您将得到“2”,而不是“@”(或 Shift + 2 将为用户的键盘布局/语言生成的任何内容)。

ToAscii()
是完美的......直到按下死键。

编辑2:这是钩子函数,删除了不相关的信息:

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);
}
windows unicode diacritics keyboard-hook
9个回答
4
投票

相当古老的线程。不幸的是,它不包含我正在寻找的答案,并且所有答案似乎都无法正常工作。在调用 MapVirtualKey /

ToUnicode
之前,我最终通过检查
ToAscii
函数的
MSB
解决了这个问题。看起来很有魅力:

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

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


4
投票

众所周知ToUnicode()

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

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

为了避免这种情况,您可以

在单独的线程中进行 ToUnicode() 调用(它将具有单独的键盘状态)或在 wFlags

 参数中使用特殊标志,该标志记录在 
ToUnicode() 文档:

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

或者您可以提前准备sc->char映射表并在

WM_INPUTLANGCHANGE

语言更改事件上更新。

我认为它也应该与

ToAscii()

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

参见

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


3
投票
    停止使用 ToAscii() 并使用 ToUncode()
  1. 请记住,ToUnicode 可能不会在死键上返回任何内容 - 这就是它们被称为死键的原因。
  2. 任何按键都会有扫描码或虚拟键码,但不一定有字符。
您不应该将按钮与字符组合起来 - 假设任何键/按钮都具有文本表示形式 (Unicode) 是错误的。

所以:

    对于
  • 输入文本使用Windows报告的字符 要检查
  • 按下的按钮
  • (例如游戏),请使用扫描代码虚拟键(可能虚拟键更好)。 对于
  • 键盘快捷键
  • 使用虚拟键代码。

2
投票

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



2
投票
ToAscii

ToUnicode
两次即可解决。 我找到了这个并将其转换为 Delphi,并且它有效!

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



1
投票

经过深入的博客搜索,我偶然发现了这个

键盘监听器

,它可以完美地处理死键。


1
投票

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



0
投票

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

这样做的优点是不会延迟操作系统的关键处理


0
投票

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 }

© www.soinside.com 2019 - 2024. All rights reserved.