为什么我启用了 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)
替换。我创建了一个测试文件来查看在 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);
问题是默认情况下,如果您使用 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
而不是 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)
我有一个 MFC 项目,它读取和写入 ANSI 文件。应用程序的字符集设置为 Unicode.
附录
我无法 change/influence 输入和输出文件的编码,因为在我的上下文中,我们正在谈论遗留软件之间的转换器。
预期的字符编码实际上是 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);
问题是默认情况下,如果您使用 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
而不是 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)