问题
最近在项目中碰到中文乱码的问题,系统是英文的,安装了中文语言包。软件界面上原生的 Win32 控件能正常显示中文,但是菜单栏,状态栏,Tab等控件中的文字都是乱码。
排查后发现是因为项目使用了多字节字符集
,恰巧程序又开启了视觉样式
功能,这意味着一些带有外观样式的控件都是自绘的,而自绘用的 CDC::DrawText 方法最终调用的 Win32 API 就是 DrawTextA,所以结果就是在绘制文本时将 GBK 编码的文本当作英文处理了,于是乱码就出现了。
最好的解决办法就是将程序修改为 Unicode 字符集,但是由于项目古老,已经没法动了。最终决定在绘制文本时手动调用 DrawTextW。
尝试用 CMFCVisualManager 解决
因为出现乱码的控件都是自绘的,所以想到一个解决办法是实现自定义样式类,并覆盖相关的绘制方法(将 MFC 源码中的绘制逻辑复制到子类中),在绘制时将DrawTextA
替换成DrawTextW
。
视觉样式功能
开启了视觉样式功能的项目。
查阅了一些资料后得知,带有外观的控件都是由 CMFCVisualManager 这个类来绘制的。
这个类实现了单例,用 CMFCVisualManager::GetInstance 获取实例。
还有一个静态方法 CMFCVisualManager::SetDefaultManager 可以设置实例,这使得我们可以实现自己的样式类。
MFC 自带的样式类。
自定义样式
要实现自己的样式很简单,从CMFCVisualManager
或任意子类继承,并覆盖以OnDraw
开头的相关方法。
1 | class MyVisualManager : CMFCVisualManager |
要改变应用外观样式时,只需要调用 CMFCVisualManager::SetDefaultManager 即可。
1 | CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(MyVisualManager)); |
并未彻底解决问题
实践发现,并不是所有自带样式的方法都可以覆盖,比如 CMFCVisualManagerOffice2007 有几个方法并非virtual
,这就没办法了。
用 Hook 方式解决
最终想到一个非常规手段,用 Hook 的方式拦截TextOutA
和DrawTextA
,这样解决绘制乱码问题更优雅。最后选择了 Detours 库来实现钩子。
代码片段:
1 | BOOL(WINAPI* OldTextOutA)(HDC, INT, INT, LPCSTR, INT) = ::TextOutA; |
完美解决。