为什么我启用了 unicode 的软件无法识别 ANSI 文件中的“Š”和其他字符?如何解决?

Why does my unicode enabled software not recognise 'Š' and other characters in ANSI files? How to fix it?

我有一个 MFC 项目,它读取和写入 ANSI 文件。应用程序的字符集设置为 Unicode.

附录
我无法 change/influence 输入和输出文件的编码,因为在我的上下文中,我们正在谈论遗留软件之间的转换器。 预期的字符编码实际上是 windows-1252.

在读写一些文件时,我注意到一些很少使用的字符,如Š (0x8A),在用CStdioFile读写时,会被? (0x3F)替换。我创建了一个测试文件来查看在 0x300xFF 之间的范围内哪些字符受到影响。

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

生成的文件看起来像 this:

变化的字符都在同一个区域,并且全部更改为0x3F '?'- 从0x800x9F。奇怪的是,有一些例外情况,如 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);

问题是默认情况下,如果您使用 CStdioFile::Open 方法,CStdioFile 只能使用 reading/writing 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.[=35 调用 CW2A()CStringW 正确转换为 CStringA =]

这是使用通用 CFile class 和 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)