为啥我的支持 unicode 的软件无法识别 ANSI 文件中的“Š”和其他字符?如何解决?
Posted
技术标签:
【中文标题】为啥我的支持 unicode 的软件无法识别 ANSI 文件中的“Š”和其他字符?如何解决?【英文标题】:Why does my unicode enabled software not recognise 'Š' and other characters in ANSI files? How to fix it?为什么我的支持 unicode 的软件无法识别 ANSI 文件中的“Š”和其他字符?如何解决? 【发布时间】:2015-11-27 18:13:05 【问题描述】:我有一个 MFC 项目,它可以从 ANSI 文件读取和写入。应用程序的字符集设置为 Unicode。
附录 我无法更改/影响输入和输出文件的编码,因为在我的上下文中,我们谈论的是旧软件之间的转换器。 预期的字符编码实际上是windows-1252。
在读写一些文件时,我注意到一些很少使用的字符,如Š (0x8A)
,在用CStdioFile
读写它们时,会被? (0x3F)
替换。我创建了一个测试文件来查看在0x30
和0xFF
之间的范围内哪些字符受到影响。
我将这些字符复制到 Testfile(ANSI 编码)(字符从 0x30 到 0xFF)
生成的文件看起来像 this:
更改的字符都在同一区域周围,并且都更改为0x3F '?'
- 从0x80
开始直到0x9F
。奇怪的是,有一些例外,例如 0x81
、0x8D
、0x90
和 0x9D
,它们没有受到影响。
测试行为的示例代码:
//prepare vars
CFileException fileException;
CStdioFile filei;
CStdioFile fileo;
CString strText;
//open input file
filei.Open(TEXT("test.txt"), CFile::modeRead | CFile::shareExclusive | CFile::typeText, &fileException);
//open output file
fileo.Open(TEXT("testout.txt"), CFile::modeCreate | CFile::modeWrite | CFile::shareExclusive | CFile::typeText, &fileException);
//read and write
BOOL eof = filei.ReadString(strText) <= 0;
fileo.Write(CStringA(strText), CStringA(strText).GetLength());
//clean up
filei.Close();
fileo.Close();
为什么要这样做,我需要做些什么来保留所有字符?
禁用 unicode 模式可以解决问题,但不幸的是,在我的情况下不是一个选项。
总结 以下是从接受的答案中摘录的对我有用的内容:
不要通过调用它的构造函数将CStringW
转换为CStringA
。从 Unicode 转换为“ANSI”(Windows1252)时,使用CW2A
:
CStringA strTextA(strText, CP_ACP)` //CP_ACP converts to ANSI
fileo.Write(strTextA, strTextA.GetLength());
更简单:使用CStdioFile::WriteString
方法而不是CStdioFile::WriteS
:
fileo.Open(TEXT("testout.txt"), CFile::modeCreate | CFile::modeWrite | CFile::shareExclusive | CFile::typeText, &fileException);
fileo.WriteString(strText);
【问题讨论】:
ANSI 编码基于代码页。除非您知道用于对文件进行编码的代码页,并且在读回文件时该代码页恰好处于活动状态,否则您无法保留字符。为避免混淆,序列化字符串流应使用 UTF-8 编码。 @IInspectable 我知道这一点 - 但可以预期它们基于西欧代码页。我实际上只是想将文件保存在与加载相同的代码页中。我无法控制文件进入的代码页。 如果您无法控制用于编写文件的代码页,则更有理由要求使用 UTF-8。这允许您通过验证其是否符合 UTF-8 来丢弃非法输入。不能使用 ANSI 编码。数据在离开/进入您的应用程序时可能只是烤面包。 @Marwie :Š
...西欧。那不计算。 S caron 用于东欧(捷克/克罗地亚)
@MSalters 澄清一下:当我谈论 ANSI 时,它是在 windows 机器上显示给我的 ansi,它在由 Beyond compare 或 notepad++ 分析的文本文件中。经过一些研究,我坚信我们在这里谈论的是一个实际的Windows-1252 character encoding,它肯定包含该集合中的 S charon。
【参考方案1】:
问题在于,默认情况下,如果您使用CStdioFile::Open
方法,CStdioFile
只能读取/写入 ANSI 文件,但您可以自己打开文件流,然后您将能够指定正确的编码:
FILE* fStream = NULL;
errno_t e = _tfopen_s(&fStream, _T("C:\\Files\\test.txt"), _T("rt,ccs=UNICODE"));
if (e != 0)
return; // failed to open file
CStdioFile f(fStream);
CString sRead;
f.ReadString(sRead);
f.Close();
如果您想写入文件,您需要使用_T("wt,ccs=UNICODE")
选项集。
另一个明显的问题是您调用Write
而不是WriteString
。对于WriteString
,无需将CStringW
转换为CStringA
。如果出于某种原因需要使用Write
,您必须通过使用CP_UTF8
调用CW2A()
将CStringW
正确转换为CStringA
。
这里是使用通用CFile
类和Write
而不是CStdioFile
和WriteString
的示例代码:
CStringW sText = L"Привет мир";
CFile file(_T("C:\\Files\\test.txt"), CFile::modeWrite | CFile::modeCreate);
CStringA sUTF8 = CW2A(sText, CP_UTF8);
file.Write(sUTF8 , sUTF8.GetLength());
请记住 CFile
打开文件的构造函数和 Write
方法抛出 CFileException
类型的异常。所以你应该处理它们。
打开文本文件流时使用以下选项指定编码类型:
"ccs=UNICODE"
对应 UTF-16 (Big endian)
"ccs=UTF-8"
对应 UTF-8
"ccs=UTF-16LE"
对应 UTF-16LE (Little endian)
【讨论】:
其实我在发现错误的时候尝试过这个方法。它有完全相同的问题 - 请注意这两个文件都是 ANSI 文件。顺便说一句,设置"ccs=ANSI"
会引发错误,根据MSDN,您必须省略指定编码以读取ANSI。
另一个问题是您使用默认 ASCII 字符集转换为CStringA
,然后调用Write()
而不是WriteString()
。像 |~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–~™ 这样的符号集肯定不属于 ASCII 字符集。我更新的答案中有更多详细信息。
啊——你对 CStringA 转换是绝对正确的——那是邪恶的事情。如果您考虑一下,相信只需删除一个字节即可从中获得 ANSI,这是非常愚蠢的,不是吗? :-) WriteString
是解决方案。所以最后它与生成 CStdioFile 的方式无关,流已经打开,但只与转换有关。您介意将此作为您的主要答案吗?只是为了完整性:我无法使用 CW2A 获得一个运行示例(与我的问题相同) - 之后我需要如何将它与 Write
一起使用?
我尝试了 CW2A 示例并收到以下警告no suitable user-defined conversion from "ATL::CW2A" to "CStringA" exists
如果您使用的是旧版本的 VS 和 ATL,则需要致电 USES_CONVERSION
以上是关于为啥我的支持 unicode 的软件无法识别 ANSI 文件中的“Š”和其他字符?如何解决?的主要内容,如果未能解决你的问题,请参考以下文章