解决 MFC 程序在英文系统上中文乱码问题
2024-07-06 00:44:11

问题

最近在项目中碰到中文乱码的问题,系统是英文的,安装了中文语言包。软件界面上原生的 Win32 控件能正常显示中文,但是菜单栏,状态栏,Tab等控件中的文字都是乱码。
排查后发现是因为项目使用了多字节字符集,恰巧程序又开启了视觉样式功能,这意味着一些带有外观样式的控件都是自绘的,而自绘用的 CDC::DrawText 方法最终调用的 Win32 API 就是 DrawTextA,所以结果就是在绘制文本时将 GBK 编码的文本当作英文处理了,于是乱码就出现了。

最好的解决办法就是将程序修改为 Unicode 字符集,但是由于项目古老,已经没法动了。最终决定在绘制文本时手动调用 DrawTextW

尝试用 CMFCVisualManager 解决

因为出现乱码的控件都是自绘的,所以想到一个解决办法是实现自定义样式类,并覆盖相关的绘制方法(将 MFC 源码中的绘制逻辑复制到子类中),在绘制时将DrawTextA替换成DrawTextW

视觉样式功能

开启了视觉样式功能的项目。

查阅了一些资料后得知,带有外观的控件都是由 CMFCVisualManager 这个类来绘制的。
这个类实现了单例,用 CMFCVisualManager::GetInstance 获取实例。
还有一个静态方法 CMFCVisualManager::SetDefaultManager 可以设置实例,这使得我们可以实现自己的样式类。


MFC 自带的样式类。

自定义样式

要实现自己的样式很简单,从CMFCVisualManager或任意子类继承,并覆盖以OnDraw开头的相关方法。

1
2
3
4
5
6
7
8
9
class MyVisualManager : CMFCVisualManager
{
DECLARE_DYNCREATE(MyVisualManager)
protected:
virtual void OnDrawTabContent(CDC* pDC, CRect rectTab, int iTab, BOOL bIsActive, const CMFCBaseTabCtrl* pTabWnd, COLORREF clrText)
{
//TODO: 自定义绘制代码
}
};

要改变应用外观样式时,只需要调用 CMFCVisualManager::SetDefaultManager 即可。

1
CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(MyVisualManager));

并未彻底解决问题

实践发现,并不是所有自带样式的方法都可以覆盖,比如 CMFCVisualManagerOffice2007 有几个方法并非virtual,这就没办法了。

用 Hook 方式解决

最终想到一个非常规手段,用 Hook 的方式拦截TextOutADrawTextA,这样解决绘制乱码问题更优雅。最后选择了 Detours 库来实现钩子。


代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
BOOL(WINAPI* OldTextOutA)(HDC, INT, INT, LPCSTR, INT) = ::TextOutA;
int(WINAPI* OldDrawTextA)(HDC, LPCSTR, int, LPRECT, UINT) = ::DrawTextA;

BOOL WINAPI FakeTextOutA(HDC hdc, int x, int y, LPCSTR lpString, int c)
{
std::wstring wstr = to_wstring(lpString);
return ::TextOutW(hdc, x, y, wstr.c_str(), wstr.size());
}

int WINAPI FakeDrawTextA(HDC hdc, LPCSTR lpchText, int cchText, LPRECT lprc, UINT format)
{
std::wstring wstr = to_wstring(lpchText);
return ::DrawTextW(hdc, wstr.c_str(), wstr.size(), lprc, format);
}

完美解决。

相关阅读

CMFCVisualManager 类
CMFCVisualManagerWindows10 自定义类