1

我的问题似乎让人们感到困惑。这是具体的:

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

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

在 MBCS 构建目标下,以上代码为代码页 932 生成了一个正确编码的文件(假设 932 是运行此代码页时的系统默认代码页)。

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

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

这是曾经存在的问题:

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

如果我的应用程序是为 MBCS 编译的,那么将 MBCS 字符串写入“wt”流会生成一个格式正确的文本文件,其中包含系统代码页的 MBCS 文本(即“用于非 Unicode 软件的代码页”)。

因为我们的软件通常使用大多数字符串和流函数的 _t 版本,所以在 MBCS 构建中,输出主要由 puts(pszMBString)或类似的东西处理putc。由于 pszMBString已经在系统代码页中(例如,在日本机器上运行时为 932),字符串是逐字写出(尽管行终止符被自动按摩putsgets

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


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


但我真正需要的是能够生成与为 MBCS 编译时能够生成的完全相同的文件:使用系统代码页的正确行终止符和 MBCS 文本文件。

显然,我可以自己手动调整行终止符并使用二进制流。然而,这是一种非常具有侵入性的方法,因为我现在必须在整个系统中找到写入文本文件的每一段代码,并对其进行修改,以便它能够正确地完成所有这些工作。让我大吃一惊的是,UNICODE 目标比我们过去使用的 MBCS 目标更愚蠢/能力更差!当然有一种方法可以切换 C 库以显示“按原样输出窄字符串但正确处理行终止符,就像您在 MBCS 构建中所做的那样”?!

4

3 回答 3

4

可悲的是,这是一个巨大的话题,值得一本专门的小书来讨论。而且那本书基本上需要一个专门的章节来针对每个希望构建的目标平台(Linux、Windows [flavor]、Mac 等)。

我的回答只涵盖 Windows 桌面应用程序,为 C++ 编译,带或不带 MFC。 请注意:这与希望使用系统默认代码页(即非 Unicode 软件的代码页)从 UNICODE 构建中读入和写出 MBCS(窄)文件有关。 如果要从 UNICODE 构建中读取和写入 Unicode 文件,则必须以二进制模式打开文件,并且必须手动处理 BOM 和换行转换(即在输入时,您必须跳过 BOM(如果有),并且两者将外部编码转换为 Windows Unicode [即 UTF-16LE],并将任何 CR+LF 序列仅转换为 LF;对于输出,您必须编写 BOM(如果有),并将 UTF-16LE 转换为任何目标编码您想要,而且您必须将 LF 转换为 CR+LF 序列才能使其成为格式正确的 PC 文本文件)。

请注意 MS 的 std C 库的 puts 和 get 以及 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()(你可以查询via _getmbcp(). 这是用于所有窄字符串解释的实际代码页(不是语言环境)(注意:这始终初始化为CP_ACP,即GetACP()由 VS C++ 启动代码)。

有用的参考资料:
- the-secret-family-split-in-windows-code-page-functions
-将其全部整理出来(说明 Windows 中有四种不同的语言环境)
- MS 提供了一些允许您设置的功能直接使用的编码,但我没有探索它们
-关于 MFC 更改的重要说明,导致它不再尊重 CP_ACP,而是默认从 MFC 7.0 开始的 CP_THREAD_ACP
-探索 Windows 中的控制台应用程序为何极端Unicode I/O 失败
- MFC/ATL 窄/宽字符串转换宏(我不使用,但您可能会发现有用)
-字节顺序标记,您需要写出任何编码的 Unicode 文件才能被其他 Windows 软件理解

于 2013-08-26T17:04:31.793 回答
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文档fopen指出:

当 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 标准库的窄字符串函数之前自己处理转换。

于 2013-08-21T21:41:49.003 回答
0

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

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

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

于 2013-08-21T14:52:08.000 回答