我无法弄清楚如何更新 WM_CHAR 处理程序以使用新的 /utf-8 执行模式 + manifest。如果我使用带有 unicode 字符的源字符串,我可以正常工作。 Windows 标题和控制台将按应有的方式显示它们,但现在我希望我的用户输入也能正确处理它。
我在任何地方都找不到有关它的文档,所以在某些时候我尝试手动找出它,但没有成功。
所以我尝试像这样打印数据:
case WM_CHAR:
println("WM_CHAR: 0x{:X}[{}]", (uint32)wParam, GetUTF8CharCount((uint32)wParam));
计数函数是这样的:
static uint GetUTF8CharCount(uint32 c) {
if (c <= 0x7F) {
/* U+0000 ... U+007F */
return 1;
} else if (c < 0x07FF) {
/* U+0080 ... U+07FF */
return 2;
} else if (c < 0xFFFF) {
/* U+0800 ... U+FFFF */
return 3;
} else {
/* U+10000 ... U+10FFFF */
return 4;
}
}
如果我在文本框中输入“ウ”,则会打印以下信息:
[Info]: WM_CHAR: 0x4581[3]
[Info]: WM_CHAR: 0x4581[3]
[Info]: WM_CHAR: 0xA6[2]
这个网站告诉我只有最后一个字节是正确的。 这对我来说毫无意义,我该如何解释这条消息?
编辑:
另外我还打印了以下信息:
CPINFOEX cpInfo;
GetCPInfoEx(CP_ACP, 0, &cpInfo);
println("ACP: {}, IsWindowUnicode: {}", GetACP(), IsWindowUnicode(hWnd));
println("CPINFOEX: {{{}, {}, {}, {}, {}}}", cpInfo.CodePage, std::string(cpInfo.CodePageName), *((uint16*)cpInfo.DefaultChar), cpInfo.MaxCharSize, (uint32)cpInfo.UnicodeDefaultChar);
结果:
[Info]: ACP: 65001, IsWindowUnicode: 0
[Info]: CPINFOEX: {65001, 65001 (UTF-8), 63, 4, 65533}
强调它确实有效(如果我直接在源代码中添加它):
编辑2:
似乎与主动语言有关。如果我将键盘布局设置为美国国际(我的默认设置),则按 Win+;输入 🎉 表情符号,我得到
F0 9F 8E 89
,但切换到日语会导致 0x4581
出现
经过几个小时的修补,我终于找到了一种适用于所有语言的方法,同时仍然能够在启用 UTF-8 代码页 并设置 /utf-8 标志的情况下运行。这个想法是将所有内容设置为 utf-8 模式(除了 Win32 窗口)。所以我们应该使用 RegisterClass(Ex)W
和
CreateWindowW
。对于窗口标题等其他 API,如果我们愿意并且仍然支持 unicode,我们可以使用 A
版本。如果设置正确WM_CHAR
将发送UTF-16字符。它可能会以代理对的形式发送它们,您需要将它们组合成一个代码点。
case WM_CHAR: {
if (IS_HIGH_SURROGATE(wParam)) {
window->highSurrogate = (uint32_t)wParam; // Store the first uint16, we will need to wait for the second WM_CHAR message to get the complete pair
break;
}
uint32_t codePoint;
if (IS_SURROGATE_PAIR(window->highSurrogate, wParam)) { // Check if we have a complete pair now
codePoint = ((window->highSurrogate - HIGH_SURROGATE_START) << 10) + ((uint32_t)wParam - LOW_SURROGATE_START) + 0x10000;
} else { // Otherwise this is already the code point (in the lower unicode range)
codePoint = (uint32_t)wParam;
}
ProcessChar(codePoint);
break;
}
请注意,您必须将 highSurrogate
存储在临时变量中,该变量可用于下次调用
WM_CHAR
回顾一下;
确保将文件放置在编译器可以找到的地方。如果不能,您不会收到错误。
app.manifest
内容:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity type="win32" name="MyAppName" version="1.0.0.0"/>
<asmv3:application>
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">
<activeCodePage>UTF-8</activeCodePage>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>
要从代码点获取 UTF-8 字符,您可以使用以下代码:
std::vector<uint8_t> ConvertCodePointToUTF8(uint32_t code) {
std::vector<uint8_t> utf8_bytes;
if (code <= 0x7F) { /* U+0000 ... U+007F */
utf8_bytes.push_back(static_cast<uint8_t>(code));
} else if (code <= 0x7FF) { /* U+0080 ... U+07FF */
utf8_bytes.reserve(utf8_bytes.size() + 2);
utf8_bytes.push_back(static_cast<uint8_t>(0xC0 | ((code >> 6) & 0x1F)));
utf8_bytes.push_back(static_cast<uint8_t>(0x80 | (code & 0x3F)));
} else if (code <= 0xFFFF) {
utf8_bytes.reserve(utf8_bytes.size() + 3); /* U+0800 ... U+FFFF */
utf8_bytes.push_back(static_cast<uint8_t>(0xE0 | ((code >> 12) & 0x0F)));
utf8_bytes.push_back(static_cast<uint8_t>(0x80 | ((code >> 6) & 0x3F)));
utf8_bytes.push_back(static_cast<uint8_t>(0x80 | (code & 0x3F)));
} else {
utf8_bytes.reserve(utf8_bytes.size() + 4);
utf8_bytes.push_back(static_cast<uint8_t>(0xF0 | ((code >> 18) & 0x07)));
utf8_bytes.push_back(static_cast<uint8_t>(0x80 | ((code >> 12) & 0x3F)));
utf8_bytes.push_back(static_cast<uint8_t>(0x80 | ((code >> 6) & 0x3F)));
utf8_bytes.push_back(static_cast<uint8_t>(0x80 | (code & 0x3F)));
}
return utf8_bytes;
}
...
auto utf8Char = ConvertCodePointToUTF8(codePoint);
std::cout << std::format("WM_CHAR Result: {}, CodePoint: 0x{:X}\n", std::string{ utf8Char.begin(), utf8Char.end() }, codePoint);