如何在Windows控制台上输出Unicode字符串
已经有几个与这个问题有关的问题。 我认为我的问题有点不同,因为我没有真正的问题,我只是出于学术兴趣。 我知道Windows的UTF-16实现有时与Unicode标准(例如排序规则)相矛盾,或者更接近旧的UCS-2而不是UTF-16,但我会在这里保留“UTF-16”术语,原因是简单。
背景:在Windows中,一切都是UTF-16。 无论你是在处理内核,图形子系统,文件系统还是其他什么,你都会传递UTF-16字符串。 Unix中没有语言环境或字符集。 为了与中世纪版本的Windows兼容,有一种称为“代码页”的东西已经过时,但仍然受到支持。 AFAIK,向控制台写入字符串只有一个正确且非过时的函数,即WriteConsoleW
,它带有一个UTF-16字符串。 此外,类似的讨论也适用于输入流,我也将忽略它。
但是,我认为这代表了Windows API中的一个设计缺陷:有一个通用函数可用于写入所有名为WriteFile
流对象(文件,管道,控制台...),但此函数是面向字节的, t接受UTF-16字符串。 该文档建议使用WriteConsoleW
进行面向文本的控制台输出,并使用WriteFile
进行面向字节的其他任何操作。 由于控制台流和文件对象都由内核对象句柄表示,并且控制台流可以重定向,因此每次写入标准输出流时都必须调用一个函数,以检查句柄是代表控制台流还是文件,从而打破多元化。 OTOH,我认为Windows在文本字符串和原始字节之间的分离(在许多其他系统(如Java或Python)中进行镜像)在概念上优于Unix的char*
方法,该方法忽略编码并且不区分字符串和字节数组。
所以我的问题是:在这种情况下该做什么? 为什么即使在微软自己的图书馆中也不能解决这个问题? .NET Framework和C和C ++库似乎都遵循过时的代码页模型。 您将如何设计Windows API或应用程序框架来规避此问题?
我认为一般问题(这个问题不容易解决)是所有的库都假定所有的流都是以字节为导向的,并且在其上面实现面向文本的流。 但是,我们发现Windows在操作系统级别上确实有特殊的面向文本的流,并且这些库无法处理此问题。 所以在任何情况下,我们都必须对所有标准库进行重大更改。 一种快速和肮脏的方法是将控制台视为一种特殊的面向字节流,只接受一种编码。 这仍然要求C和C ++标准库必须规避,因为它们没有实现WriteFile
/ WriteConsoleW
开关。 那是对的吗?
我/我们在大多数(跨平台)应用程序/项目中使用的一般策略是:我们只是在任何地方都使用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
和某些日志文件中。 如果你对如何完成这项工作感兴趣,请查看代码。
几点:
我绝不会说代码页已经过时。 也许Windows开发人员会希望他们如此,但他们永远不会。 所有的世界,但Windows API,使用面向字节的流来表示数据:XML,HTML,HTTP,Unix等使用编码,最流行和最强大的是UTF-8。 所以你可以在内部使用宽字符串,但在外部世界中,你需要别的东西。
甚至当你打印wcout << L"Hello World" << endl
它在大多数系统(但是窗口)上被转换为UTF-8的字节导向流。
我个人认为,微软在将每个地方的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中的一个设计缺陷
以下两篇文章对此进行了相当好的讨论。
所以我的问题是:在这种情况下该做什么? 为什么即使在微软自己的图书馆中也不能解决这个问题? .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的来源。 这里唯一的答案是使用那些过时的功能。
链接地址: http://www.djcxy.com/p/30297.html