我的问题似乎让人们感到困惑。这里有一些具体的东西:
我们的代码执行以下操作:
FILE * fout = _tfsopen(_T("丸穴種類.txt"), _T("w"), _SH_DENYNO);
_fputts(W2T(L"刃物種類n"), fout);
fclose(fout);
在MBCS构建目标下,上面为代码页932生成了一个正确编码的文件(假设932是运行时的系统默认代码页)。
在UNICODE构建目标下,上面生成了一个垃圾文件,其中充满了????。
我想定义一个符号,或者使用编译器开关,或者包含一个特殊的头,或者链接到一个给定的库,以便在构建目标为UNICODE时继续执行上述操作,而不更改源代码。
这是过去存在的问题:
CCD_ 1流可以以t(translated)或b(inary)模式打开。桌面应用程序可以编译为UNICODE或MBCS(在Windows)。
如果我的应用程序是为MBCS编译的,那么将MBCS字符串写入"wt"流生成格式良好的文本文件,其中包含MBCS文本对于系统代码页(即非Unicode的代码页软件")
因为我们的软件通常使用大多数字符串&流函数,在MBCS构建中,输出主要由CCD_ 2或类似的CCD_
pszMBString
已经在系统代码页中(例如932在日本机器上运行),字符串被逐字逐句地写出来(尽管线路终端由puts
和gets
按摩自动)。但是,如果我的应用程序是为UNICODE编译的,那么编写MBCS字符串到"wt"流会导致垃圾(大量"????"字符)(即我将UNICODE转换为系统的默认代码页面,然后使用例如,
fwrite(pszNarrow, 1, length, stream)
)。我可以以二进制模式打开流,在这种情况下,我将获得更正MBCS文本。。。但是,线路终止器将不再PC风格的CR+LF,但将只是UNIX风格的LF。这是因为在二进制(非翻译)模式下,文件流不处理LF->CR+LF翻译。
但我真正需要的是,能够生成与以前为MBCS编译时完全相同的文件:correct行终止符和MBCS文本文件
很明显,我可以自己手动调整线路终端,并使用二进制流。然而,这是一种非常具有侵入性的方法,正如我现在所说必须在整个系统中找到编写文本的每一位代码文件,并对其进行更改,使其正确完成所有这些操作。什么风在我看来,UNICODE的目标比MBCS目标我们曾经使用过!当然有一种方法可以切换C库表示"按原样输出窄字符串,但处理行正确地终止,就像你在MBCS构建中所做的一样"?!
遗憾的是,这是一个巨大的主题,值得专门写一本小书。而这本书基本上需要为每个想要构建的目标平台(Linux、Windows[风味]、Mac等)专门写一章。
我的答案只涉及Windows桌面应用程序,无论是否使用MFC都是为C++编译的。请注意:这与想要使用系统默认代码页(即非UNICODE软件的代码页)从UNICODE构建读取和写入MBCS(窄)文件有关如果您想从Unicode构建中读取和写入Unicode文件,您必须以二进制模式打开文件,并且必须手动处理BOM和换行转换(即,在输入时,您必须跳过BOM(如果有),并将外部编码转换为Windows Unicode[即UTF-16LE],以及仅将任何CR+LF序列转换为LF;对于输出,您必须编写BOM(如果有),并从UTF-16LE转换为您想要的任何目标编码,此外,您必须将LF转换为CR+LF序列,使其成为格式正确的PC文本文件)
注意MS的std C库的put-and-gets和fwrite等,如果以文本/翻译模式打开,它们将在写入时将任何0x0D转换为0x0A 0x0D序列,在读取时将反之亦然,无论您是在读取还是写入单个字节、宽字符还是随机二进制数据流——它都不在乎,所有这些函数都可以归结为在文本/翻译模式下进行盲字节转换
还要注意,许多Windows API函数在内部使用CP_ACP,而对其行为没有任何外部控制(例如WritePrivateProfileString()
)。因此,我们可能希望确保所有库都使用相同的字符区域设置:CP_ACP,而不是其他某个字符区域设置,因为您无法控制某些函数的行为,所以您必须遵守它们的选择,或者根本不使用它们。
如果使用MFC,则需要:
// force CP_ACP *not* CP_THREAD_ACP for MFC CString auto-conveters!!!
// this makes MFC's CString and CStdioFile and other interfaces use the
// system default code page, instead of the thread default code page (which is normally "c")
#define _CONVERSION_DONT_USE_THREAD_LOCALE
对于C++和C库,必须告诉库使用系统代码页:
// force C++ and C libraries based on setlocale() to use system locale for narrow strings
// (this automatically calls setlocale() which makes the C library do the same thing as C++ std lib)
// we only change the LC_CTYPE, not collation or date/time formatting
std::locale::global(std::locale(str(boost::format(".%||") % GetACP()).c_str(), LC_CTYPE));
在包括任何其他头文件之前,我在所有预编译头文件中执行#define
。我在main中设置了全局语言环境(或其道德等价物),为整个程序设置了一次(您可能需要为每个要进行I/O或字符串转换的线程调用此语言环境)。
构建目标是UNICODE,对于大多数I/O,我们在通过FILE*
0输出之前使用显式字符串转换。
还有一件事需要注意,在VS C++下的C标准库中有两组不同的多字节函数,它们使用线程的语言环境进行操作,和另一个使用_setmbcp()
的集合(您可以通过_getmbcp()
查询。这是用于所有窄字符串解释的实际代码页(而不是区域设置)(注意:它总是由VS C++启动代码初始化为CP_ACP
,即GetACP()
)。
有用的参考资料:
-windows代码页函数中的秘密族拆分
-整理所有内容(解释了Windows中有四种不同的区域设置)
-MS提供了一些功能,允许您将编码设置为直接使用,但我没有探索它们
-关于MFC更改的一个重要注意事项,该更改导致它不再尊重CP_ACP,而是默认情况下从MFC 7.0开始的CP_THREAD_ACP
-探究为什么Windows中的控制台应用程序在Unicode I/O方面极端失败
-MFC/ATL窄/宽字符串转换宏(我不使用,但您可能会发现它很有用)
-字节顺序标记,您需要为任何编码的Unicode文件编写该标记,以供其他Windows软件
C库同时支持窄(char
)和宽(wchar_t
)字符串。在Windows中,这两种类型的字符串分别称为MBCS(或ANSI)和Unicode。
即使您已经定义了_UNICODE
,也完全可以使用窄函数。无论是否定义了_UNICODE
,以下代码都应该产生相同的输出:
FILE* f = fopen("foo.txt", "wt");
fputs("foonbarn", f);
fclose(f);
在您的问题中,您写道:"我将UNICODE转换为系统的默认代码页,并将其写入流中"。这让我相信,您的宽字符串包含无法转换到当前代码页的字符,因此用问号替换每个字符。
也许您可以使用当前代码页之外的其他编码。我建议尽可能使用UTF-8编码。
更新:在运行代码页1252的Windows机器上测试示例代码时,对_fputts
的调用返回-1,表示有错误。puts(pszMBString)
0被设置为EILSEQ
,意思是"非法字节序列"。fopen
的MSDN文档指出:
当Unicode流I/O功能在文本模式下运行时(默认),则假定源流或目标流是一个序列的多字节字符。因此,Unicode流输入函数将多字节字符转换为宽字符(就像通过调用CCD_ 23函数)。出于同样的原因,Unicode流输出函数将宽字符转换为多字节字符(就像通过调用
wctomb
函数)。
这是此错误的关键信息。wctomb
将使用C标准库的语言环境。通过明确地将C标准库的区域设置为代码页932(Shift-JIS),代码运行良好,输出文件中的输出正确地用Shift-JIS编码。
int main()
{
setlocale(LC_ALL, ".932");
FILE * fout = _wfsopen(L"丸穴種類.txt", L"w", _SH_DENYNO);
fputws(L"刃物種類n", fout);
fclose(fout);
}
另一种(也许更可取的)解决方案是在调用C标准库的窄字符串函数之前自己处理转换。
当您为UNICODE编译时,c++库对MBCS一无所知。如果您说您打开文件以输出文本,它将尝试将您传递给它的缓冲区视为UNICODE缓冲区。
此外,MBCS是可变长度编码。为了解析它,c++库需要对字符进行迭代,当它对MBCS一无所知时,这当然是不可能的。因此,不可能"仅仅正确地处理行终止符"。
我建议您事先准备好字符串,或者制作自己的函数,将字符串写入文件。不确定逐个写入字符是否有效(需要测量),但如果不有效,则可以分段处理字符串,将不包含的所有内容一次性处理。