如何在Windows控制台上输出Unicode字符串

时间:2010-06-28 08:29:07

标签: windows unicode console

已经有一些与此问题相关的问题。我认为我的问题有点不同,因为我没有实际问题,我只是在考虑学术兴趣。我知道Windows的UTF-16实现有时与Unicode标准(例如整理)相矛盾,或者更接近旧UCS-2而不是UTF-16,但我会在这里保留“UTF-16”术语,原因是简单。

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

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

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

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

4 个答案:

答案 0 :(得分:5)

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

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

我们的Windows版本中并没有真正使用控制台(在Windows版本中,所有控制台输出都包含在文件中)。由于我们到处都有UTF8,我们的控制台输出也是UTF8,适用于大多数现代系统。此外,Windows控制台日志文件的内容为UTF8,Windows上的大多数文本编辑器都可以正常读取。

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

如果您想知道如何实现这样的自动管道处理程序:我们已经为所有类似POSIX的系统实现了这样的功能。代码可能不适用于Windows,但我认为应该可以移植它。我们当前的管道处理程序与WriteConsoleW类似。即如果您执行tee,它将同时打印在cout << "Hello" << endl和某个日志文件中。如果您对此有何兴趣,请查看the code

答案 1 :(得分:4)

有几点:

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

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

  3. 我个人认为,微软在每个地方都将API改为广泛而不是在任何地方支持UTF-8时都会犯错。你当然可以争论它。但实际上你必须将文本和面向字节的流分开并在它们之间进行转换。

答案 2 :(得分: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仍然使用Ansi,Multi-Byte或Unicode构建ability。这包括使用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的源代码。这里唯一的答案是使用那些过时的功能。

答案 3 :(得分:0)

我的工作如何正确如下:

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