在Windows下,是使用宽字符还是UTF-8?

3
我们正在将Windows代码从传统字符集转换为Unicode。我们的GUI代码使用MFC,但我们还有许多非GUI模块将被并入非MFC环境中。
UTF-8是保存数据文件的最具未来性的方式吗?
Windows系统调用必须使用宽字符字符串,否则它们将被解释为遗留代码页。对于程序中的一般字符串,是更好地使用宽字符字符串(与系统调用和MFC兼容)还是UTF-8(如果我们采用这种方式,则与数据文件兼容)?
我们如何最小化UTF-8字符串被解释为遗留代码页的风险?我们过去曾遇到海外用户跨代码页问题,摆脱这一问题是我们转向完全Unicode的动机之一。

3
关于程序内部编码的选择,至少在调用Win32 APIs时,您没有任何选择。您必须使用UTF-16。而对于外部数据文件,完全取决于您自己的选择。UTF-8通常是一个不错的选择,但这取决于您的需求。 - David Heffernan
2
http://www.utf8everywhere.org/ - Simple
我想说的和 @DavidHeffernan 一样,只是我想先说出来,这样他就会重复我的陈述,而不是相反。因为这是客观上最好的方法。谢谢。 - Cheers and hth. - Alf
@Cheersandhth.-Alf,你让我笑了! - David Heffernan
1
@jalf:嗯,它并不是完全分离的,只是很好地分离了。特别是当将字符串传递到适用于Windows的API函数或库函数中时,会对效率产生影响,并且在处理由此类API或通用库函数生成的字符串时,会对复杂性产生影响。为了移植原始的Unix代码,这种影响可能比整体代码修改的影响要小。但是,当任务是将旧代码转换为新代码时,可以选择既高效又低复杂度的方式(更少的错误,更少的工作)。 - Cheers and hth. - Alf
显示剩余2条评论
5个回答

2
很不幸,Windows 中的情况有点丑陋。尽管内部标准化采用 Unicode,但在许多情况下,文本文件仍然使用当前代码页进行解释。
UTF-8 是文件的一个好选择,因为它允许在使用不同语言的 Windows 系统以及 Linux 及其相关系统之间交换数据。您可以通过在文件开头放置 字节顺序标记(BOM) 来增加正确解释 UTF-8 文件的机会。这并不是一个完美的解决方案;并非所有程序都能识别它,并且它违反了 Unicode 标准的建议。
Windows API 使用 UTF-16 作为其 Unicode 接口。除非您喜欢逆流而上,否则我建议在内部程序使用中坚持使用它。

2
在应用程序中,你有两个基本模型:
  • 在整个应用程序中使用UTF-16。
  • 在整个应用程序中使用UTF-8字符串,并在Win32 API / MFC / ...调用时进行UTF-16的转换。
如果你将大量使用不支持UTF-16的库,则第一个模型可能会有问题。但实际上,我从未发现这是一个问题。有些人会告诉你,仅仅因为你使用UTF-16,你就是愚蠢的,你的产品注定失败,但我从未发现这是一个问题。
如果你屈服于同行的压力,或者依赖于现有的以UTF-8为中心的代码,则在使用自定义包装类将字符串转换为/从CString(以及一些处理[out] CString */CString &的帮助类)时,使用UTF-8内部可以得到简化。对于非MFC非CString代码,std::vector<TCHAR>是一个很好的表示形式。当然,该包装器不应隐式地转换为/从char *或wchar_t *。
在读写文件时,只要它们是“你的”应用程序文件,你可以随心所欲地处理。事实上,使用不透明(二进制)格式可能会完全隔离用户问题。只需保持一致即可。
问题出现在你开始处理来自其他应用程序的文件,或者用户可以使用其他应用程序编辑你的应用程序文本文件时。这就是它开始变得黯淡的地方。由于多年来对UTF-8的支持非常有限,因此许多工具无法很好地处理它。其他程序确实能够正确识别和解释UTF-8,但无法跳过任何存在的BOM标记。
尽管如此,UTF-8是“未来的安全投注”。即使它需要更多的开发,我强烈建议在共享文件中使用它。
我们的解决方案经过一番讨论之后,如下所示:
读取文本文件时,默认算法为:
  • 探测BOM。如果存在,则依赖于BOM(但当然要跳过它)
  • 探测有效的UTF-16(我们甚至支持LE / BE,尽管BE不太可能出现)。
  • 探测ASCII(所有字节<= 127)。如果是,则将其解释为ASCII。
  • 探测UTF-8。如果正文是有效的UTF-8,则读取为UTF-8。
  • 否则回退到当前代码页。
UTF-8是专门设计的,以便任何其他编码实际上都是有效的UTF-8的可能性非常非常低。这使得最后两个步骤的顺序相当安全。
在写入文本文件时,我们使用没有BOM的UTF-8。从我们使用的外部工具的简短、信息性调查来看,这是最安全的选择。

基于此,我们还包括了一种工具,使我们的开发人员和用户可以检测和转换非UTF-8文本文件为UTF-8。


ASCII是UTF-8的子集,因此如果您的探针显示所有字符都<= 127,则可以安全地将数据解释为UTF-8。换句话说,您的“仅探测ASCII”步骤是多余的,因为ASCII已经包含在“探测UTF-8”的步骤中。 - Remy Lebeau
@RemyLebeau:是的,但在这种情况下,我们使用另一种(更简单)的转换路径。 - peterchen

1
UTF-8是保存数据文件的最具未来性的方式吗?
实际上没有使用其他方式的理由。
Windows系统调用必须使用宽字符字符串,否则它们将被解释为遗留代码页。
您还可以使用包装器将Win32 API调用与接受UTF-8字符串并在调用UTF-16本机API之前将其转换的shim一起使用。
对于程序中的一般字符串,使用宽字符字符串(与系统调用和MFC兼容)还是UTF-8(如果我们采用这种方式,则与数据文件兼容)更好?
这真的取决于情况。您不希望在代码中散布转换,因为这更容易导致转换遗漏。
如果程序具有复杂的内部逻辑,则希望您已经组织好了输入/输出代码以及与系统API交互的代码,这样您就可以选择任何一条路线:在API使用上放置转换或在IO操作上放置转换。如果系统API使用和IO尚未本地化,请先修复该问题。
如果程序逻辑足够简单,不需要本地化其中一个,则将转换放在更本地化的那个上。您还可以重构程序,使其中一个本地化以便于转换。
如何最小化UTF-8字符串被解释为遗留代码页的风险?我们过去曾与海外用户存在跨代码页问题,摆脱这种问题是我们迁移到完整Unicode的动机之一。
建立一致的标准并加以执行。要求所有非wchar_t字符串都是UTF-8,并且不使用任何使用遗留编码的第一方或第三方API。如果您的工具链允许您禁用API(例如通过“已弃用”属性),则对找到并删除其用法的API执行此操作。确保开发人员都了解字符串编码,并确保代码审查人员注意编码错误。

一个注意事项:我认为Windows不一致地执行Unicode规则,例如,文件名可能包含非法的Unicode 16位字符序列。根据您的操作方式,这样的文件名可能无法完整地转换为UTF-8并返回。(我在计算机帐户密码和Windows设置的unattend.xml文件中遇到了这个问题。我不确定它是否适用于文件名,但我相信它是这样的。) - Harry Johnston

0

我同意@DavidHeffernan关于API的观点,我也建议完全切换到Unicode(我们为所有应用程序采取了这种方式,这是一次性的努力,长期来看会得到回报)


0

正如Mark Ransom已经回答的那样,以及David Heffernan和我已经评论过的那样,UTF-16是Windows程序内部的实际选择,而UTF-8则是外部表示的非常好的选择(除了交互式控制台i/o,但这并不是什么大问题)。

由于您正在从旧代码进行转换,因此我想重点关注可重用性

通过不直接盲目使用wchar_t,而是使用条件定义为例如类型Syschar,可以使潜在的平台无关的可重用部分真正可重用。

enum Syschar: wchar_t {};    // For Windows, implying UTF-16

并且作为

enum Syschar: char {};       // For Linux-land, implying UTF-8

使用enum而不是struct可以确保您可以使用该类型来专门化std::basic_string(当您定义适当的std::char_traits时),即使其实现使用联合进行短缓冲区优化。

正如David Wheeler所说,“计算机科学中的所有问题都可以通过另一级间接性来解决” - 这就是其中之一。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接