使用SendInput发送超过U+FFFF的Unicode字符

14

我正在编写一个类似于Windows 8中的屏幕键盘。使用Win32的SendInput发送大部分所需字符没有问题。

问题出现在新的Windows 8表情符号上。它们从U+1F600开始,使用Segoe UI Symbol字体。

在Windows 8屏幕键盘上使用Spy ++获取所有表情符号的输出如下:

<00001> 000C064A P WM_KEYDOWN nVirtKey:VK_PACKET cRepeat:1 ScanCode:00 fExtended:0 fAltDown:0 fRepeat:0 fUp:0
<00002> 000C064A P WM_CHAR chCharCode:'63' (63) cRepeat:1 ScanCode:00 fExtended:0 fAltDown:0 fRepeat:0 fUp:0
<00003> 000C064A P WM_KEYUP nVirtKey:VK_PACKET cRepeat:1 ScanCode:00 fExtended:0 fAltDown:0 fRepeat:1 fUp:1
<00004> 000C064A P WM_KEYDOWN nVirtKey:VK_PACKET cRepeat:1 ScanCode:00 fExtended:0 fAltDown:0 fRepeat:0 fUp:0
<00005> 000C064A P WM_CHAR chCharCode:'63' (63) cRepeat:1 ScanCode:00 fExtended:0 fAltDown:0 fRepeat:0 fUp:0
<00006> 000C064A P WM_KEYUP nVirtKey:VK_PACKET cRepeat:1 ScanCode:00 fExtended:0 fAltDown:0 fRepeat:1 fUp:1

由于它们都产生相同的输出,我看不到实际上标识唯一字形的发送内容是什么。

我知道SendInput有一个KEYEVENTF_UNICODE参数用于发送Unicode字符。但是这些字符似乎在某种扩展的Unicode页面中。超出了16位Unicode范围(U+0000至U+FFFF),C# char或INPUT结构中的short wScan值可以表示。

这是我的SendCharUnicode方法。

public static void SendCharUnicode(char ch)
{
    Win32.INPUT[] input = new Win32.INPUT[2];

    input[0] = new Win32.INPUT();
    input[0].type = Win32.INPUT_KEYBOARD;
    input[0].ki.wVk = 0;
    input[0].ki.wScan = (short)ch;
    input[0].ki.time = 0;
    input[0].ki.dwFlags = Win32.KEYEVENTF_UNICODE;
    input[0].ki.dwExtraInfo = Win32.GetMessageExtraInfo();

    input[1] = new Win32.INPUT();
    input[1].type = Win32.INPUT_KEYBOARD;
    input[1].ki.wVk = 0;
    input[1].ki.wScan = (short)ch;
    input[1].ki.time = 0;
    input[1].ki.dwFlags = Win32.KEYEVENTF_UNICODE | Win32.KEYEVENTF_KEYUP;
    input[1].ki.dwExtraInfo = Win32.GetMessageExtraInfo();

    Win32.SendInput(2, input, Marshal.SizeOf(typeof(Win32.INPUT)));
}

我该如何修改这个方法才能成功发送Unicode字符,比如(U+1F600)?


你能使用 WM_UNICHAR 吗? - Cory Nelson
2
我已经解决了这个问题,只是没有足够的积分来发布答案。解决方案是将UTF-32字符分解为其UTF-16代理对,并使用SendInput发送它们。 - headkaze
1个回答

9

我用 API Monitor 监控了 Windows 8 屏幕键盘,确实使用了 SendInput。经过进一步调查,我发现需要将 UTF-32 Unicode 字符拆分为它的 UTF-16 代理对,例如 U+1F604 变成 [U+D83D U+DE04]。因此,如果我发送 D83D 然后是 DE04,就可以成功发送 U+1F604。

以下是一个可行的方法:

public static void SendCharUnicode(int utf32)
{
    string unicodeString = Char.ConvertFromUtf32(utf32);
    Win32.INPUT[] input = new Win32.INPUT[unicodeString.Length];

    for (int i = 0; i < input.Length; i++)
    {
        input[i] = new Win32.INPUT();
        input[i].type = Win32.INPUT_KEYBOARD;
        input[i].ki.wVk = 0;
        input[i].ki.wScan = (short)unicodeString[i];
        input[i].ki.time = 0;
        input[i].ki.dwFlags = Win32.KEYEVENTF_UNICODE;
        input[i].ki.dwExtraInfo = IntPtr.Zero;
    }
    
    for (int i = 0; i < input.Length; i++)
    {
        input[i] = new Win32.INPUT();
        input[i].type = Win32.INPUT_KEYBOARD;
        input[i].ki.wVk = 0;
        input[i].ki.wScan = (short)unicodeString[i];
        input[i].ki.time = 0;
        input[i].ki.dwFlags = Win32.KEYEVENTF_UNICODE | Win32.KEYEVENTF_KEYUP;
        input[i].ki.dwExtraInfo = IntPtr.Zero;
    }

    Win32.SendInput((uint)input.Length, input, Marshal.SizeOf(typeof(Win32.INPUT)));
}

没有64位应用程序的问题。问题在于此代码无法发送“KEYEVENTF_KEYUP”输入事件。 - David Heffernan
我确认它可以使用向下和向上的输入事件。正确的事件顺序是 [symb1_down, symb2_down, symb1_up, symb2_up]。计算代理对的算法在这里:https://unicodebook.readthedocs.io/unicode_encodings.html#utf-16-surrogate-pairs - Vasily Ryabov
好的,谢谢。我已经更新了示例代码以反映这一点。 - headkaze

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接