如何在Windows控制台上输出Unicodestring

已经有几个与这个问题有关的问题了。 我觉得我的问题有点不一样,因为我没有实际的问题,只是出于学术上的兴趣。 我知道Windows的UTF-16实现有时与Unicode标准(例如sorting规则)相矛盾,或者更接近旧的UCS-2而不是UTF-16,但是我将在这里保留“UTF-16”简单。

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

但是,我认为这代表了Windows API中的一个devise缺陷:有一个通用函数可以用来写入所有名为WriteFilestream对象(文件,pipe道,控制台),但是这个函数是面向字节的, t接受UTF-16string。 文档build议使用WriteConsoleW作为面向文本的控制台输出,而使用WriteFile作为面向字节的其他内容。 由于控制台stream和文件对象都由内核对象句柄表示,并且控制台stream可以redirect,所以必须调用一个函数,以便每次写入标准输出stream时检查该句柄是代表控制台stream还是文件,从而打破多元化。 OTOH,我认为Windows在文本string和原始字节(在许多其他系统,如Java或Python中被镜像)之间的分离在概念上优于Unix的char*方法,忽略了编码,不区分string和字节数组。

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

我认为一般的问题(不容易解决)是所有的库都假设所有的stream都是以字节为导向的,并且在其上面实现面向文本的stream。 但是,我们发现Windows在操作系统级别上有特殊的面向文本的stream,而这些库无法处理这一点。 所以在任何情况下,我们都必须对所有标准库进行重大更改。 一种快速和肮脏的方法是将控制台视为一种特殊的面向字节的stream,只接受一种编码。 这仍然要求C和C ++标准库必须被规避,因为它们没有实现WriteFile / WriteConsoleW开关。 那是对的吗?

Solutions Collecting From Web of "如何在Windows控制台上输出Unicodestring"

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

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

控制台并没有真正用于我们的Windows版本(在Windows版本中,所有的控制台输出都被封装到一个文件中)。 因为我们在任何地方都有UTF8,所以我们的控制台输出是UTF8,对于大多数现代系统来说都是很好的。 此外,Windows控制台日志文件的内容为UTF8,Windows上的大多数文本编辑器都可以正常读取。

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

如果您想知道如何实现这样的自动管道处理程序:我们已经为所有类POSIX系统实现了这样的事情。 该代码可能无法正常工作在Windows上,但我认为应该可以将其移植。 我们现在的管道处理器类似于tee所做的。 也就是说,如果你做一个cout << "Hello" << endl ,它将被打印在stdout和一些日志文件中。 如果你感兴趣的话,看看代码如何完成。

几点:

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

    甚至当你打印wcout << L"Hello World" << endl时,在大多数系统(但是窗口)上UTF-8都将其转换为面向字节的流。

  3. 我个人的看法是,微软在每个地方改变了他们的API,而不是在任何地方支持UTF-8,都犯了错误。 当然你可能会争论。 但事实上,你必须将文本和字节流分开,并在它们之间进行转换。

要回答您的第一个问题,您可以使用_setmode将Unicode字符串输出到Windows控制台。 有关这方面的具体细节可以在Michael Kaplan的博客上找到。 默认情况下,控制台不是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应用程序,但这并不完全准确。

一个更好的例子是司机。 驱动程序可以用Unicode或Ansi编写。 这真的取决于界面的性质。 为了进一步说明这一点,Microsoft提供了StrSafe库,该库专门用内核模式驱动程序编写,它包含Unicode和Ansi版本 。 虽然驱动程序是Ansi或Unicode,但是Windows内核必须正确地与它们进行交互,而不管它们采取什么形式。

从Windows的核心越远,互操作性越好。 这包括代码页和区域设置 。 你必须记住,不是所有的软件都是用Unicode编写的。 Visual C ++ 2010仍然具有使用Ansi,Multi-Byte或Unicode构建的能力 。 这包括使用代码页和区域设置 ,它们是C / C ++标准的一部分。

不过,我认为这代表了Windows API中的一个设计缺陷

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

  • 传统智慧迟缓,又名@#%&*是_O_U16TEXT?
  • 头文件没有延迟,又名什么@#%&*是_O_WTEXT?

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

在这一点上,我认为你事后看过Windows。 Unicode不是第一个, ASCII做的。 在ASCII之后,出现了代码页 。 在代码页之后,来到DBCS 。 DBCS 传入MBCS后 (最终UTF-8)。 UTF-8之后,出现了Unicode (UTF-16 / UCS-2)。

多年来,这些技术中的每一项都被整合到Windows操作系统中。 每个建筑物上最后一个,但没有打破对方。 软件是为了这些而编写的。 虽然有时候看起来不像是微软,但是微软还是投入了大量的精力来打破它没有写的软件。 即使是现在,你也可以编写新的软件,利用这些技术中的任何一种技术,它将起作用。

这里真正的答案是“兼容性”。 微软仍然使用这些技术,其他许多公司也是如此。 有许多程序,组件和库没有被更新(或将被更新)以使用Unicode。 即使像.NET这样的新技术出现,旧技术也必须坚持下去。 至少在互操作性方面。

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

我如何纠正工作如下:

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