Windows 中的 Unicode 规范化

Posted

技术标签:

【中文标题】Windows 中的 Unicode 规范化【英文标题】:Unicode Normalization in Windows 【发布时间】:2011-10-25 20:12:54 【问题描述】:

我一直在 Windows 中使用“unicode 字符串”……我已经了解了 Unicode(例如,毕业后)。然而,Win32API 非常松散地提到“unicode”总是让我感到困惑。特别是,MSN 提到的“unicode”变体是 UTF-16(尽管“宽字符”术语来自它曾经是 UCS-2,而不是 Unicode)。但是,它几乎没有提到 Unicode 规范化。

MSN 有几页关于Unicode 和Unicode Normalization Forms 以及change the normalization form 的功能。规范化页面甚至说:

Win32 和 .NET Framework 支持所有四种规范化形式。

但是,我在文档中的任何地方都没有找到 Win32 API 使用(或理解)什么规范化形式。

问题一:用户输入(如Edit控件)和通过MultiByteToWideChar()转换默认使用什么规范化形式?

问题 2:传递给 Win32API 函数的字符串必须采用特定的规范化形式,还是与内核和文件系统规范化无关?

【问题讨论】:

我认为您的 Q1 与不相关的想法混为一谈:转换函数仅在同一逻辑字符串的 unicode 代码点(例如 UTF8 和 UTF16)的不同二进制表示之间转换。但是,规范化是一个高级概念,仅涉及代码点的逻辑序列。两者没有任何关系。特别是,MultiByteToWideChar 只会为您提供与您输入它相同的代码点序列,只是采用不同的编码。我想这也回答了 Q2。 确实,MultiByteToWideChar() 的文档说它只是直接映射任何输入。来自备注部分:“考虑在使用 MultiByteToWideChar 转换后调用 NormalizeString。NormalizeString 提供更准确、标准和一致的数据,并且还可以更快。” @KerrekSB:很抱歉恢复这个非常古老的线程,但我今天再次偶然发现它并重新阅读了您的评论。问题是,您假设 UTF-8 到 UTF-16 转换,MultiByteToWideChar 允许使用 CP_ACP 转换为 UTF-16,其中可能包含一些具有多个代码点和规范化形式的非 ASCII 字符(例如 é )。 【参考方案1】:

首先,感谢您提出了一个很好的问题。我在Michael Kaplan's blog找到了答案:

但由于 Windows 上的所有文本输入方法都倾向于使用相同的规范化形式(形式 C),...

【讨论】:

不错的发现。尽管 Michael 是 Microsoft 的开发人员,但至少可以说,这段话是相当非官方的。知道这是否记录在官方的某个地方吗? @André Caron 虽然 Michael Kaplan 的博客不一定是官方的,但它包含一些关于 Windows 上的 Unicode / 国际化的最佳信息。在过去的几年里,我的每一个 Unicode 问题都不可避免地导致了他的博客。【参考方案2】:

来自 MSDN 文章 Using Unicode Normalization to Represent Strings。

Windows、Microsoft 应用程序和 .NET Framework 通常使用普通输入法以 C 格式生成字符。对于 Windows 上的大多数用途,形式 C 是首选形式。例如,形式 C 中的字符是由 Windows 键盘输入产生的。但是,从 Web 和其他平台导入的字符可以将其他规范化形式引入数据流中。

更新:我已经包含了与问题 #2 相关的一些具体细节。

关于文件系统,不需要规范化 - 基于文章Naming Files, Paths, and Namespaces。

无需对路径和文件名字符串执行任何 Unicode 规范化以供 Windows 文件 I/O API 函数使用,因为文件系统将路径和文件名视为不透明的 WCHAR 序列。您的应用程序所需的任何规范化都应牢记这一点,而不是对相关 Windows 文件 I/O API 函数的任何调用。

关于 SQL Server,不需要规范化 - nor is data normalized when saved in the database。也就是说,在比较字符串时,SQL Server 2000 在索引内部使用its own string normalization 机制;但我找不到关于那是什么的具体细节。 SQL Server 2005 文章states the same。

SQL Server 7.0 中的一个重要变化是为字符串比较提供了一个独立于操作系统的模型,以便从 Windows 95 到 Windows 2000 的所有操作系统之间的排序规则保持一致。此字符串比较代码基于 Windows 2000 用于其自己的字符串规范化的相同代码,并且被封装为在所有计算机和所有 SQL Server 版本中都相同。

【讨论】:

我接受这个答案,因为它指的是官方文档。但是,所有答案的结论是,系统函数返回的字符串通常采用 C 格式,并不能真正保证是这种情况。如果需要特定的规范化形式,则应手动规范化所有字符串。【参考方案3】:

用户输入默认使用什么规范化形式

取决于您的键盘布局/IME。如果您愿意,可以生成标准形式 C、D 或两者的疯狂混合。

键盘布局倾向于 NFC,因为在 Unicode 之前的日子里,他们通常会在本地代码页中为每个按键输出一个单字节字符。不过也有例外。

例如,使用 Windows 越南语键盘布局,一些变音符号被键入为与字母组合的单个按键(例如抑扬符â),而另一些被键入为组合变音符号(例如坟墓)。 graheme a-with-circumflex-and-grave 将输入为 a-circumflex,然后是组合坟墓ầ,在越南语代码页 1258 中为 0xE2,0xCC,输出为 U+00E2,U +0300 以 Unicode 表示。

这不是标准形式 C(即 U+1EA7 带有抑扬符和坟墓的拉丁小写字母 A),也不是 D(即 ầ U+0061,U+0302,U+0300 )。

在 Windows 世界和网络上普遍存在对 NFC 的文化偏好,而在 Apple 世界中则对 NFD 存在偏好。但它并没有严格执行,您应该期望处理组合和分解字符的任何混合。

内核和文件系统是否与规范化无关?

是的,内核和文件系统对规范化一无所知,并且很乐意让您在同一个文件夹中拥有名称为 ầ.txtầ.txtầ.txt 的文件。

【讨论】:

关于你的最后一点:如果内核和文件系统可能仍然以两种规范化形式区分相同的字符串,这样它就可以防止你有两个具有“相同”名称“à”的文件NFC 和 NFD。这就是我所说的“规范化不可知论”的意思,指的是同等地处理所有 unicode 形式。 也许“规范化-无知”是一种更清晰的表达方式:对于 Windows,它们只是一堆代码点。它试图做的唯一“聪明”的事情是不区分大小写地匹配它们。这已经够棘手了,因为大小写折叠规则在不同的 Unicode 版本中发生了变化! 我已经有一段时间没有问这个问题了,我正在重新阅读你的帖子。也许我应该问的第二个问题是:“内核 Unicode 是否智能”?例如,如果您请求一个在 NFD 中具有名称的文件,如果该文件是使用 NFC 中的路径(或混合,或其他)创建的,它会匹配吗? @André:不,确实,“聪明”不是。 NFC 和 NFD 字符串在字符串处理级别上通常是不同的,特别是在 NTFS 文件系统中。所以,是的,让用户手动键入要匹配的文件路径可能会很痛苦。但至少当你从文件系统中读回文件名时,你会以你输入的相同形式得到它......在 OS X (HFS+/UFS) 上不是这种情况,它会强制所有内容到 NFD,导致讨厌的互操作问题。 确实,SVN 有(有?)nasty problem with NFC VS. NFD storage。我认为阅读这个问题是首先引发这个问题的原因。将文件名读回原始编码是一个很好的属性,但它与不同规范化形式的字符串之间的正确比较是正交的。我确定 ICU 有一个字符串比较函数,它不需要两个字符串采用相同的规范化形式。

以上是关于Windows 中的 Unicode 规范化的主要内容,如果未能解决你的问题,请参考以下文章

Windows:支持哪个版本的 Unicode?

哪个是更好的 Unicode 规范化形式?

规范化 Unicode

如何在 Java 中规范化 Unicode 数字

File.listFiles() 使用 JDK 6 破坏 unicode 名称(Unicode 规范化问题)

何时使用 Unicode 规范化表格 NFC 和 NFD?