为啥我的支持 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) 替换。我创建了一个测试文件来查看在0x300xFF 之间的范围内哪些字符受到影响。

我将这些字符复制到 Testfile(ANSI 编码)(字符从 0x30 到 0xFF)

生成的文件看起来像 this:

更改的字符都在同一区域周围,并且都更改为0x3F '?'- 从0x80 开始直到0x9F。奇怪的是,有一些例外,例如 0x810x8D0x900x9D,它们没有受到影响。

测试行为的示例代码:

//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 而不是CStdioFileWriteString 的示例代码:

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 文件中的“Š”和其他字符?如何解决?的主要内容,如果未能解决你的问题,请参考以下文章

文件名包括路径名称中的中文无法识别,

为啥WPF无法识别我的这个DynamicResource

为啥 Javascript 无法识别我的构造函数?

为啥我的程序无法识别我的方法?

为啥qq提取文字识别失败

为啥 Xcode 无法识别我的核心数据实体的属性