2013-08-21 23 views
0

我的问题似乎困扰了人们。下面是具体的东西:如何从UNICODE应用程序写入MBCS文件?

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

FILE * fout = _tfsopen(_T("丸穴種類.txt"), _T("w"), _SH_DENYNO); 
_fputts(W2T(L"刃物種類\n"), fout); 
fclose(fout); 

在MBCS构建目标,上述产生的代码页932(假设932是系统默认代码页时,这是一个正确编码文件跑)。

在UNICODE构建目标下,上面产生了一个充满了????的垃圾文件。

我想定义一个符号,或者使用一个编译器开关,或者包含一个特殊的头文件或链接到一个给定的库,以使上述内容在编译目标是UNICODE时不需要改变源代码就能继续工作。

这里的,因为它曾经存在这样的问题:

FILE*流可以在叔打开(ranslated)或b(inary)模式。 桌面应用程序可以编译为UNICODE或MBCS(在Windows的 下)。

如果我的应用程序被编译为MBCS,然后写MBCS字符串到 “重量”流中包含MBCS文本 为系统代码页格式良好的文本文件的结果(即代码页“非Unicode的 软件”)。

因为我们的软件普遍采用的最串& 流功能_t版本,在MBCS建立输出由 puts(pszMBString)或类似的东西putc等主要处理由于 pszMBString已经在系统代码页(如932,当运行在日本机器上的 ),字符串被逐字写出 (尽管线终结符自动按putsgets )。

但是,如果我的应用程序被编译为UNICODE,然后写MBCS 字符串为“重量”流导致垃圾(大量的“?????” 字符)(即我的UNICODE转换为系统的默认代码 ,页面,然后使用例如 fwrite(pszNarrow, 1, length, stream))将其写入流。


我可以打开我的二进制模式流,在这种情况下,我会得到正确的 MBCS文本......但是,该行终止不再是 PC风格的CR + LF,而是将只是UNIX样式的LF。这是因为 处于二进制(非转换)模式,因此文件流不处理LF-> CR + LF转换。


但我真正需要的,是能够产生我以前可以编译为MBCS时产生完全相同的文件:正确 行结束符和MBCS文本文件使用系统的代码页。

显然我可以自己手动调整行终止符,并使用 二进制流。但是,这是一种非常有侵入性的方法,因为我现在需要在整个系统中找到编写文本 文件的所有代码,并对其进行修改,以便正确执行所有这些操作。我脑海中挥之不去的是,UNICODE的目标是否比我们曾经使用过的MBCS目标更能干!当然,有一种方法可以将C 库切换为“原样输出窄字符串,但正确处理行终止符,正如您在MBCS编译中所做的一样”?!

+0

你的计划是,如果你需要写一个未在当前MBCS编码所能表述的Unicode字符?如果你绝对需要坚持使用MBCS,为什么还要用'UNICODE'编译(大概是'_UNICODE')呢?或者为什么不直接调用“ANSI”版本的函数?就个人而言,我将切换到UTF-8作为数据文件格式,并提供一个迁移工具来转换现有的数据。 – jamesdlin

+0

请提供一个简短的代码示例。 – dalle

+0

您是否使用特定于Windows的_t函数宏,如_tfopen和_fputts或fopen/_wfopen和fputs/fputws函数? – dalle

回答

3

不幸的是,这是一个巨大的话题,值得一本专门讨论它的小书。那本书基本上需要为每个目标平台(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的性病C库的看跌期权,并得到和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,我们在通过CStringA(my_wide_string)输出之前使用显式字符串转换。

一个人应该知道的,也有在VS C中的C标准库++两套不同的多字节函数的另一件事 - 那些使用线程的语言环境对他们的行动,而另一组,其使用一种叫做_setmbcp() (您可以通过_getmbcp()查询这是用于所有窄字符串的解释(注:实际的代码页(而不是区域):这个总是被VS C++启动代码)初始化为CP_ACP,即GetACP()

有用的参考资料:
- the-secret-family-split-in-windows-code-page-functions
- Sorting it all out (explains that there are four different locales in effect in Windows)
- MS offers some functions that allow you to set the encoding to use directly, but I didn't explore them
- An important note about a change to MFC that caused it to no longer respect CP_ACP, but rather CP_THREAD_ACP by default starting in MFC 7.0
- Exploration of why console apps in Windows are extreme FAIL when it comes to Unicode I/O
- MFC/ATL narrow/wide string conversion macros (which I don't use, but you may find useful)
- Byte order marker, which you need to write out for Unicode files of any encoding to be understood by other Windows software

+0

无论谁投票,这需要说明为什么。欢迎您提出意见,但您至少需要说明为什么您认为这是一个糟糕的答案,否则就会让我自己和任何未来的读者对我们项目中正常工作产生负面影响我有很多时间/努力去侦察)。 – Mordachai

+1

我会建议重写代码不使用boost,因为不是每个人都会拥有它,但我明白了,它基本上是在执行setlocale(LC_CTYPE,“.codepage”);其中codepage是OS当前的默认系统代码页。 )。我要提到的第二件事是,如果您使用MFC DLL,因为它已经被预编译为二进制文件,所以_CONVERSION_DONT_USE_THREAD_LOCALE不会起作用。所以SetThreadLocale(LOCALE_SYSTEM_DEFAULT)在某些情况下变得有必要。 –

0

当你为UNICODE编译时,C++库对MBCS一无所知。如果你说你打开文件输出文本,它会尝试将你传递给它的缓冲区视为UNICODE缓冲区。

此外,MBCS是可变长度编码。为了解析它,C++库需要迭代字符,当它对MBCS一无所知时,这当然是不可能的。因此,“仅仅正确处理行终止符”是不可能的。

我建议你要么预先准备好你的字符串,要么把你自己的函数写入字符串文件。不确定一个一个地写字符是否有效(需要进行测量),但如果不是,则可以分段处理字符串,将所有不包含\ n的字符集中在一起。

+0

C库(在VC++ 2012下)有一个数字用于处理MBCS和UNICODE的机制,例如包括'fputs'与'fputws'。我所要求的可能不被支持,但我认为你的回答并没有显示出对Windows下C库的深入了解,而且我正在寻找某人的反馈。 – Mordachai

+1

@Mordachai:如果你认为'fputs'处理'MBCS',那么你对MBCS没有深入的了解。我相信这也是你问题/问题的根源。 –

+0

如果我这么认为,我会是一个白痴(因为docs明确指出fputs不能处理MBCS和UNICODE,但fputws DOES也如文档中所述)。 – Mordachai

2

C库支持窄(char)和宽(wchar_t)字符串。在Windows中,这两种类型的字符串分别称为MBCS(或ANSI)和Unicode。

尽管已经定义了_UNICODE,但完全可以使用窄功能。下面的代码应该产生相同的输出,不管_UNICODE定义与否:

FILE* f = fopen("foo.txt", "wt"); 
fputs("foo\nbar\n", f); 
fclose(f); 

在你的问题,你写道:“我的Unicode字符转换成系统的默认代码页和写入到流”。这使我相信,你的宽字符串包含无法转换为当前代码页的字符,因此用问号替换它们中的每一个。

也许你可以使用一些其他的编码,而不是当前的代码页。我建议尽可能使用UTF-8编码。

更新:在代码页1252上运行的Windows计算机上测试您的示例代码,_fputts的调用返回-1,指示错误。 errno被设置为EILSEQ,这意味着“非法字节序列”。该MSDN documentationfopen指出:

当Unicode流的I/O功能中的文本模式( 默认)操作时,源或目标流被假定为多字节字符的序列 。因此,Unicode流输入函数 将多字节字符转换为宽字符(就像通过调用 函数mbtowc函数一样)。出于同样的原因,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标准库的窄字符串函数之前自己处理的转换。

+0

如果一个字符串确实包含一个不可编码的字符,那么是的,我期望?导致。这里不是这种情况 - 我正在查看明显可以在Shift-JIS中编码的字符,来自Shift-JIS文件(并且已正确加载并转换为UNICODE)。但是当试图将它们写回来时,它们变成了? – Mordachai

+0

我感谢你的回答,@dalle。这基本上是正确的。对我们来说,最大的问题是上述内容将日语的其他方面(而不仅仅是字符编码)改变为日本(代码页932)。我们需要尊重用户期望的设置,以进行整理和日期/时间格式化等。,同时也缩小了文件I/O“正确”(即与我们MBCS版本中的相同)。查看我的答案获取更多信息。 – Mordachai

相关问题