【问题标题】:How to Output Unicode Strings on the Windows Console如何在 Windows 控制台上输出 Unicode 字符串
【发布时间】:2011-03-09 01:00:01
【问题描述】:

已经有一些与此问题相关的问题。我认为我的问题有点不同,因为我没有实际问题,我只是出于学术兴趣而问。我知道 Windows 的 UTF-16 实现有时与 Unicode 标准(例如排序规则)相矛盾,或者更接近旧的 UCS-2 而不是 UTF-16,但出于以下原因,我将在此处保留“UTF-16”术语简单。

背景:在 Windows 中,一切都是 UTF-16。无论您是在处理内核、图形子系统、文件系统还是其他任何东西,您都在传递 UTF-16 字符串。 Unix 意义上没有语言环境或字符集。为了与中世纪版本的 Windows 兼容,有一种称为“代码页”的东西已过时但仍受支持。 AFAIK,只有一个正确且不会过时的函数可以将字符串写入控制台,即WriteConsoleW,它采用 UTF-16 字符串。此外,类似的讨论适用于输入流,我也将忽略。

但是,我认为这代表了 Windows API 中的一个设计缺陷:有一个通用函数可以用于写入所有流对象(文件、管道、控制台……),名为 WriteFile,但这个函数是 byte -面向并且不接受 UTF-16 字符串。文档建议使用WriteConsoleW 用于控制台输出,它是面向文本的,WriteFile 用于其他所有东西,它是面向字节的。由于控制台流和文件对象都由内核对象句柄表示,并且可以重定向控制台流,因此您必须为每次写入标准输出流调用一个函数,以检查句柄是表示控制台流还是文件,从而破坏多态性。 OTOH,我确实认为 Windows 在文本字符串和原始字节之间的分离(在 Java 或 Python 等许多其他系统中镜像)在概念上优于 Unix 的 char* 方法,该方法忽略编码并且不区分字符串和字节数组。

所以我的问题是:在这种情况下该怎么办?为什么即使在微软自己的库中也没有解决这个问题? .NET Framework 以及 C 和 C++ 库似乎都遵循过时的代码页模型。您将如何设计 Windows API 或应用程序框架来规避此问题?

我认为一般问题(不容易解决)是所有库都假设所有流都是面向字节的,并在此基础上实现面向文本的流。但是,我们看到 Windows 在操作系统级别确实有特殊的面向文本的流,而库无法处理这个问题。所以无论如何,我们必须对所有标准库进行重大更改。一种快速而肮脏的方法是将控制台视为仅接受一种编码的特殊面向字节的流。这仍然要求必须绕过 C 和 C++ 标准库,因为它们没有实现 WriteFile/WriteConsoleW 开关。对吗?

【问题讨论】:

标签: windows unicode console


【解决方案1】:

我/我们在大多数(跨平台)应用程序/项目中使用的一般策略是:我们只在所有地方使用 UTF-8(我的意思是真正的标准)。我们使用 std::string 作为容器,我们只是将 everything 解释为 UTF8。我们也以这种方式处理所有文件 IO,即我们期望 UTF8 并保存 UTF8。如果我们从某个地方得到一个字符串并且我们知道它不是 UTF8,我们会将它转换为 UTF8。

我们偶然发现 WinUTF16 的最常见情况是文件名。因此,对于每个文件名处理,我们总是将 UTF8 字符串转换为 WinUTF16。如果我们在目录中搜索文件,也是另一种方式。

在我们的 Windows 构建中并没有真正使用控制台(在 Windows 构建中,所有控制台输出都包装到一个文件中)。由于我们到处都有 UTF8,我们的控制台输出也是 UTF8,这对于大多数现代系统来说都很好。而且 Windows 控制台日志文件的内容为 UTF8,Windows 上的大多数文本编辑器都可以毫无问题地读取。

如果我们会更多地使用 WinConsole 并且如果我们非常关心所有特殊字符是否正确显示,我们可能会编写一些自动管道处理程序,我们将其安装在 fileno=0 和真正的 stdout 之间,它将使用WriteConsoleW 正如你所建议的那样(如果真的没有更简单的方法的话)。

如果您想知道如何实现这样的自动管道处理程序:我们已经为所有类似 POSIX 的系统实现了这样的事情。该代码可能无法在 Windows 上正常运行,但我认为应该可以移植它。我们当前的管道处理程序类似于tee 所做的。 IE。如果您执行cout << "Hello" << endl,它将同时打印在stdout 和一些日志文件中。如果您对这是如何完成的感兴趣,请查看the code

【讨论】:

    【解决方案2】:

    几点:

    1. Windows“WriteConsoleW”和 printf 之间的一个重要区别是 WriteConsoleW 将控制台视为 GUI 而不是文本流。例如,如果您使用它并使用管道,您将不会捕获输出。
    2. 我永远不会说代码页已经过时。也许 Windows 开发人员希望他们如此,但他们永远不会。除了 windows api 之外,所有世界都使用面向字节的流来表示数据:XML、HTML、HTTP、Unix 等使用编码,最流行和最强大的一种是 UTF-8。因此,您可以在内部使用 Wide 字符串,但在外部世界中您将需要其他东西。

      即使您打印 wcout << L"Hello World" << endl 它也是 在大多数系统(但 Windows)上,在引擎盖下转换为面向字节的流 转为 UTF-8。

    3. 我个人的看法是,微软在将他们的 API 全部更改为 Wide 而不是在所有地方都支持 UTF-8 时确实犯了错误。当然,你可能会为此争论不休。但实际上您必须将面向文本和面向字节的流分开并在它们之间进行转换。

    【讨论】:

    • 1. Microsoft 建议在使用 WriteConsole 之前检查标准输出流是否进入控制台或其他东西。这很麻烦,但似乎是唯一可行且可移植的选择。 2. 代码页和编码不一样。对于代码页,我指的是 Windows 控制台代码页。由于 Windows 控制台是面向文本的并使用 UTF-16,因此代码页已过时 - 每个使用代码页的字符串无论如何都会立即转换为 UTF-16。 wostream 问题很不幸,但由 C++ 标准规定。 3. 我不认为使用 UTF-16 的决定是不幸的......
    • ...吃了,但是 API 设计得很糟糕。例如,您可以想到类似GetStdHandle(STD_UTF16LE_OUTPUT_HANDLE) 的东西,它将返回一个面向字节的流句柄,该句柄需要 UTF-16-LE 编码的字符串。然后你可以在任何地方使用WriteFile。 OTOH,我认为 C 和 C++ 没有真正的文本流的问题更重要。
    • 我认为“全世界,除了 windows api,使用面向字节的流来表示数据”有点夸大其词。 Java、C# 和 JavaScript 也将其所有字符和字符串处理作为面向单词的流 UTF-16。
    • @hippetrail (a) 最后,当您将数据写入文件时,您几乎从不使用 UTF-16。 (b) 我说的是操作系统级别的 API——除了 windows 之外的任何地方都是面向字节的——操作系统提供了你的控制台抽象 (c) Java、JavaScript、C# 最终提供了一些字符编码来将文本转换为输出流。
    • 当微软将他们的 API 拆分为代码页 (A) 和宽字符 (W) 变体时,UTF-8 还没有被发明出来。很难指责他们没有对甚至不存在的东西进行标准化。不过,我确实觉得他们本可以做更多的事情来让 UTF-8 正常工作。
    【解决方案3】:

    要回答您的第一个问题,您可以使用 _setmode 将 Unicode 字符串输出到 Windows 控制台。有关这方面的具体细节可以在Michael Kaplan's blog 上找到。默认情况下,控制台不是 Unicode (UCS-2/UTF-16)。它以 Ansi(区域设置/代码页)方式工作,并且必须专门配置为使用 Unicode。

    另外,您必须更改控制台字体,因为默认字体仅支持 Ansi 字符。这里有一些小例外,例如零扩展的 ASCII 字符,但打印实际的 Unicode 字符需要使用 _setmode。

    在 Windows 中,一切都是 UTF-16。无论您是在处理内核、图形子系统、文件系统还是其他任何东西,您都在传递 UTF-16 字符串。没有 Unix 意义上的语言环境或字符集。

    这并不完全正确。虽然 Windows 的底层核心确实使用 Unicode,但有大量的互操作性发挥作用,让 Windows 与各种软件进行交互。

    考虑一下记事本(是的,记事本远不是核心组件,但它让我明白了)。记事本能够读取包含 Ansi(您当前的代码页)、Unicode 或 UTF-8 的文件。您可能认为记事本是一个 Unicode 应用程序,但这并不完全准确。

    一个更好的例子是驱动程序。 Drivers 可以用 Unicode 或 Ansi 编写。这实际上取决于接口的性质。为了进一步说明这一点,Microsoft 提供了 StrSafe 库,该库是专门为 Kernel-mode drivers 编写的,它包括 both Unicode and Ansi versions。虽然驱动程序是 Ansi 或 Unicode,但 Windows 内核必须正确地与它们交互,无论它们采用何种形式。

    您离 Windows 的核心越远,互操作性就越重要。这包括code pages and locales。您必须记住,并非所有软件都是用 Unicode 编写的。 Visual C++ 2010 仍然有 ability 使用 Ansi、多字节或 Unicode 构建。这包括使用 code pageslocales,它们是 C/C++ 标准的一部分。

    但是,我认为这代表了 Windows API 的设计缺陷

    以下两篇文章对此进行了很好的讨论。

    所以我的问题是:在这种情况下该怎么办?为什么即使在微软自己的库中也没有解决这个问题? .NET Framework 以及 C 和 C++ 库似乎都遵循过时的代码页模型。您将如何设计 Windows API 或应用程序框架来规避此问题?

    在这一点上,我认为您正在查看hindsight 中的 Windows。 Unicode 没有先出现,ASCII 出现了。在 ASCII 之后,出现了 code pages。在代码页之后,出现了DBCS。在 DBCS 之后是 MBCS(最终是 UTF-8)。在 UTF-8 之后,出现了 Unicode (UTF-16/UCS-2)。

    多年来,这些技术中的每一项都被整合到了 Windows 操作系统中。每栋楼都在最后,但没有互相破坏。编写软件时考虑到了这些。虽然有时看起来不像,但微软将huge amount of effort 放入破坏它没有编写的软件。即使是现在,您也可以编写利用任何这些技术的新软件,并且可以正常工作。

    这里真正的答案是“兼容性”。微软仍在使用这些技术,许多其他公司也是如此。有无数的程序、组件和库尚未更新(或将永远更新)以使用 Unicode。即使出现了较新的技术(如 .NET),旧技术也必须继续存在。至少在互操作性方面。

    例如,假设您有一个需要从 .NET 与之交互的 DLL,但该 DLL 是使用 Ansi(单字节代码页本地化)编写的。更糟糕的是,您没有 DLL 的源代码。这里唯一的答案是使用那些过时的功能。

    【讨论】:

      【解决方案4】:

      我的纠正方法如下:

      • 在内部使用 UTF-16 和 wchar_t,这通常适用于文件名和 Windows API。
      • 将代码页设置为 65001,即 UTF-8。这可确保当您读取纯文本文件时,Windows 会检查它们的 UTF-16 和 BOM(“Windows 标准”),如果没有 BOM,则文本将被视为 UTF-8(“世界标准”)并进行翻译转为 UTF-16 供您使用。

      【讨论】:

        猜你喜欢
        • 2019-03-11
        • 1970-01-01
        • 1970-01-01
        • 2013-11-20
        • 2012-03-11
        • 2012-08-16
        • 2010-11-17
        • 2011-05-16
        • 1970-01-01
        相关资源
        最近更新 更多