C++开发中ExtTextOutW无法正常显示部分Unicode字符问题咨询
问题描述
开发环境为Visual Studio 2022,使用C++开发Win32桌面应用,代码中已添加#define UNICODE宏定义。调用ExtTextOutW()、TextOutW()、DrawText()接口时文本显示异常,复现代码如下:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { // ... 省略其他代码 case WM_PAINT: PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1)); RECT rec; SetRect(&rec, 10, 10, 100, 100); ExtTextOutW(hdc, 0, 0, NULL, &rec, L"中文字", 3, nullptr); EndPaint(hwnd, &ps); return 0; } return DefWindowProcW(hwnd, uMsg, wParam, lParam); }
具体异常表现:
L"abc"这类ASCII范围内字符可正常显示- 中文字符无法正常渲染,显示为乱码
L"\u210E"、L"\U0001D465"及Unicode数学字母符号区块内的其他字符,全部显示为|符号,传入N个字符就显示N个竖线
根本原因
- 设备上下文(HDC)默认选入的系统字体字形覆盖范围有限,仅支持ASCII及少量Latin字符,不包含中文、特殊数学符号等非Latin字符的字形定义,缺字时GDI会渲染缺字占位符(表现为乱码或竖线)。
- 部分开发者将
UNICODE宏定义放在Windows头文件引入语句之后,导致部分隐式调用的接口匹配到ANSI版本,引发编码转换错误。 - 码位超过U+FFFF的Unicode字符(如数学字母符号区块的大部分字符)在UTF-16编码中以代理对形式存储,占2个WCHAR长度,若传入接口的字符长度参数按码位计数而非实际WCHAR计数,会出现渲染异常。
解决方案
按照以下优先级操作即可解决问题:
- 统一配置Unicode字符集
不要手动在代码中零散添加#define UNICODE,直接在VS项目属性页中配置:依次打开「配置属性 → 高级 → 字符集」,选择使用Unicode字符集,该配置会自动在编译参数中添加UNICODE和_UNICODE两个宏定义,且作用于所有源码文件,避免宏定义位置错误导致的接口匹配问题。 - 绘制文本前为HDC选入支持目标字符集的字体
这是解决该问题的核心操作,手动创建覆盖中文、目标特殊符号的字体(如微软雅黑、Segoe UI Symbol等),选入HDC后再执行文本绘制,修改后的WM_PAINT逻辑参考如下:
case WM_PAINT: PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1)); // 创建字体,字符集参数使用DEFAULT_CHARSET保证特殊符号可被正确识别 HFONT hFont = CreateFontW( 20, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"Microsoft YaHei UI" ); // 选入自定义字体,暂存原有字体用于后续恢复 HGDIOBJ hOldFont = SelectObject(hdc, hFont); RECT rec; // 适当放大绘制矩形范围,避免文字被裁剪 SetRect(&rec, 10, 10, 300, 100); // 注意字符长度参数传实际WCHAR数量:"中文字 "占4个,\u210E占1个,空格占1个,\U0001D465占2个,合计8个 ExtTextOutW(hdc, 10, 10, 0, &rec, L"中文字 \u210E \U0001D465", 8, nullptr); // 恢复DC原有资源,删除自定义字体避免GDI资源泄漏 SelectObject(hdc, hOldFont); DeleteObject(hFont); EndPaint(hwnd, &ps); return 0;
- 正确计算补充平面字符的长度参数
GDI的W系列文本接口使用UTF-16编码,所有码位大于U+FFFF的字符都需要用2个WCHAR的代理对存储,传入接口的字符长度参数必须填写实际的WCHAR总数,不能按视觉上的单个字符计数。 - 复杂场景替换为DirectWrite渲染
传统GDI接口对高版本Unicode字符、彩色Emoji、复杂书写脚本的支持有限,如果后续需要渲染更多特殊字符、需要更好的文本渲染效果,可以替换为DirectWrite接口实现文本绘制,字形覆盖范围和渲染质量都优于GDI。
内容的提问来源于stack exchange,提问作者Tony Ma
相关产品推荐
相关产品推荐

