如何从UNICODE应用程序编写MBCS文件



我的问题似乎让人们感到困惑。这里有一些具体的东西:

我们的代码执行以下操作:

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在日本机器上运行),字符串被逐字逐句地写出来(尽管线路终端由putsgets按摩自动)。

但是,如果我的应用程序是为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一无所知时,这当然是不可能的。因此,不可能"仅仅正确地处理行终止符"。

我建议您事先准备好字符串,或者制作自己的函数,将字符串写入文件。不确定逐个写入字符是否有效(需要测量),但如果不有效,则可以分段处理字符串,将不包含的所有内容一次性处理。

相关内容

  • 没有找到相关文章

最新更新