更改了非Unicode语言,为何中文还乱码?
acmilan2016/08/13软件综合 IP:四川

本人喜欢使用英文系统,为了避免老软件乱码以及不能输入中文的问题,一般需要将系统区域设置中的“非Unicode程序的语言”设为简体中文,如图所示:

0.png

正确设置“非Unicode程序的语言”以后,基本上所有的中文程序都能正常运行了。

正因为如此,本人经常向本论坛、远景论坛、贴吧等的其他网友推荐英文版Windows,但是有些网友反映,有一小部分程序即使更改了也不行,这是怎么回事呢?

由于这是一种非常罕见的乱码,很长时间没有找到答案,直到我在使用VC++2008带的MFC时,发现它重现了这种乱码:

1.png

这种乱码是怎么造成的呢?我发现,乱码的地方都是用CStringA::LoadStringA从资源段字符串表加载的。于是,我在这个函数下了函数断点。发现LoadStringA有多个重载,其中一个重载如下:

<code class="language-cpp">	_Check_return_ BOOL LoadString( _In_ HINSTANCE hInstance, _In_ UINT nID )
	{
		const ATLSTRINGRESOURCEIMAGE* pImage = AtlGetStringResourceImage( hInstance, nID );
		if( pImage == NULL )
		{
			return( FALSE );
		}

		int nLength = StringTraits::GetBaseTypeLength( pImage->achString, pImage->nLength );
		PXSTR pszBuffer = GetBuffer( nLength );
		StringTraits::ConvertToBaseType( pszBuffer, nLength, pImage->achString, pImage->nLength );
		ReleaseBufferSetLength( nLength );

		return( TRUE );
	}
</code>

发现它是由资源段直接读取Unicode字符串,再由ConvertToBaseType转换到ANSI字符串的,右击ConvertToBaseType转到定义,得到内容如下:

<code class="language-cpp">	static void ConvertToBaseType(_Out_cap_(nDestLength) _CharType* pszDest, _In_ int nDestLength,
		_In_count_(nSrcLength) const wchar_t* pszSrc, _In_ int nSrcLength = -1) throw()
	{		
		// nLen is in XCHARs
		::WideCharToMultiByte(_AtlGetConversionACP(), 0, pszSrc, nSrcLength, pszDest, nDestLength, NULL, NULL);
	}
</code>

ConvertToBaseType就是包装了WideCharToMultiByte函数而已,其中_AtlGetConversionACP函数决定了转换的代码页,右击它继续查找定义,得到:

<code class="language-cpp">inline UINT WINAPI _AtlGetConversionACP() throw()
{
#ifdef _CONVERSION_DONT_USE_THREAD_LOCALE
	return CP_ACP;
#else
	return CP_THREAD_ACP;
#endif
}
</code>

会不会是CP_THREAD_ACP的问题呢?我查了一下,它跟随ThreadLocale而变化。我尝试在MFC初始化之前插入一个GetThreadLocale调用:

<code class="language-cpp">// 唯一的一个 CLuanMaTest2App 对象

// 在theApp对象初始化前插入如下代码
class BeforeApp {
public:
	BeforeApp()
	{
		CString str;
		str.Format("%x", GetThreadLocale());
		MessageBox(NULL, str, "ThreadLocale", MB_OK);
	}
}beforeApp;

CLuanMaTest2App theApp;
</code>

6.png

至此真相大白——409是英文的LocaleID,804是简体中文的LocaleID。

稍后奉上更深度的分析以及解决方案

[修改于 8年4个月前 - 2016/08/14 18:44:34]

来自:计算机科学 / 软件综合
3
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
acmilan 作者
8年4个月前 修改于 8年4个月前 IP:四川
824423

从上面的分析来看,CP_THREAD_ACP是罪魁祸首——它跟随ThreadLocale变化,而ThreadLocale跟随系统用户界面语言变化,导致了不匹配。

微软也给开发者留了一个解决办法,只要在stdafx.h最前面加上这样一句就行了:

<code class="language-cpp">#define _CONVERSION_DONT_USE_THREAD_LOCALE 1
</code>

也可以将程序编译为使用Unicode字符集的程序,遇到需要多字节字符串的地方再手工转换,这样也可以避免这个问题。

至于微软为什么要挖这个坑,就不得而知了。就本人分析而言,这可能属于对Windows操作系统的复杂性考虑不足导致的误用现象。VC++2002最早出现这种现象。

引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
acmilan作者
8年4个月前 修改于 8年4个月前 IP:四川
824424

经过更深入的实验,发现只要是控制台程序,ThreadLocale就跟随“非Unicode程序的语言”,而只要是GUI程序,ThreadLocale就跟随用户界面语言。下面的程序展示了实验方法。

<code class="language-cpp">// LuanMaConsoleTest2.cpp : 定义控制台应用程序的入口点。
//
#undef UNICODE
#undef _UNICODE
#include "stdafx.h"

#include <windows.h>
#include <atlbase.h>
#include <atlstr.h>

// 英文系统返回804
#pragma comment(linker, "/SUBSYSTEM:CONSOLE /ENTRY:mainCRTStartup")
// 英文系统返回409
//#pragma comment(linker, "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup")

int _tmain(int argc, _TCHAR* argv[])
{
	CString str;
	str.Format("%x", GetThreadLocale());
	MessageBox(NULL, str, "ThreadLocale", MB_OK);
	return 0;
}
</atlstr.h></atlbase.h></windows.h></code>

至此,一个适用于最终用户的特殊乱码解决方法产生了。

适用于最终用户的解决方法

**第一个方案:**安装语言包。对不同用户帐户设置不同的显示语言,在需要运行这些程序的时候切换到使用简体中文的用户帐户,就不会乱码了。这也是最推荐的做法。

**第二个方案:**用十六进制编辑器修改exe。将exe改成控制台程序,ThreadLocale就不会跟随用户界面语言,而是跟随非Unicode程序语言,乱码问题也就解决了。如果使用的是Win7家庭版或专业版、Win8/8.1/10单语言版,就只能用这个方法了。

首先,下载一个十六进制编辑器,用它打开,找到"PE"这个字符串第一次出现的地方,光标放在字母P上面:

2.png

然后用编辑器的跳转功能,向前跳转5C(十六进制)字节,这是PE可执行文件的Subsystem字段的偏移(32位和64位程序通用)。

3.png

这个位置的原始值应该是02 00即GUI程序(如果不是就错了),将它改为03 00即控制台程序:

4.png

保存文件,重新运行,这时乱码已经不存在了。

5.png

美中不足是运行时有个黑色的控制台窗口,不过使用时可以将它最小化,不影响正常使用。

**第三个方案:**无意中发现的,安装IE11也可以解决这个问题。

引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
acmilan作者
8年4个月前 修改于 8年4个月前 IP:四川
824425

这种问题,经常出现在以下条件下:

  1. 程序是GUI程序
  2. 程序用VC++2002以上版本的MFC
  3. 程序使用了CString的字符串转换功能,比如用CStringA::LoadStringA从资源段字符串表加载字符串

最终的原因:

  1. 程序是GUI程序
  2. 程序使用CP_THREAD_ACP虚拟代码页转换字符串,而不是使用正确的CP_ACP
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

所属专业
所属分类
上级专业
同级专业
acmilan
进士 学者 笔友
文章
461
回复
2934
学术分
4
2009/05/30注册,5年10个月前活动
暂无简介
主体类型:个人
所属领域:无
认证方式:邮箱
IP归属地:未同步
文件下载
加载中...
{{errorInfo}}
{{downloadWarning}}
你在 {{downloadTime}} 下载过当前文件。
文件名称:{{resource.defaultFile.name}}
下载次数:{{resource.hits}}
上传用户:{{uploader.username}}
所需积分:{{costScores}},{{holdScores}}下载当前附件免费{{description}}
积分不足,去充值
文件已丢失

当前账号的附件下载数量限制如下:
时段 个数
{{f.startingTime}}点 - {{f.endTime}}点 {{f.fileCount}}
视频暂不能访问,请登录试试
仅供内部学术交流或培训使用,请先保存到本地。本内容不代表科创观点,未经原作者同意,请勿转载。
音频暂不能访问,请登录试试
支持的图片格式:jpg, jpeg, png
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}