经典控制台 API 与虚拟终端序列
我们建议用“虚拟终端序列”替换经典“Windows 控制台 API”。 本文将概述这两者之间的区别,并讨论我们提出建议的原因。
定义
经典 Windows 控制台 API 图面定义为 kernel32.dll
上的一系列 C 语言功能接口,名称中包含“控制台”。
虚拟终端序列定义为嵌入到标准输入和标准输出流中的命令语言。 虚拟终端序列使用不可打印的转义字符来发出与正常可打印文本交错的命令信号。
History
Windows 控制台为客户端命令行应用程序提供了广泛的 API 图面,以操控输出显示缓冲区和用户输入缓冲区。 但是,其他非 Windows 平台从未对其命令行环境提供这种特定的 API 驱动方法,而是选择使用嵌入在标准输入和标准输出流中的虚拟终端序列。 (有一段时间,Microsoft 在 DOS 和 Windows 的早期版本中也曾通过名为 ANSI.SYS 的驱动程序支持此行为。)
与此相反,虚拟终端序列(以各种方言)驱动所有其他平台的命令行环境操作。 这些序列源于 ECMA 标准以及许多供应商的一系列扩展,这些扩展可以追溯到 Digital Equipment Corporation 和 Tektronix 终端,再到更新式更常见的软件终端,如 xterm。 虚拟终端序列域中存在许多扩展,其中某些序列比其他序列得到更广泛的支持,但是可以肯定地说,世界已经将其作为命令行体验的命令语言进行了标准化,几乎每个终端和命令行客户端应用程序都支持一个已知的子集。
跨平台支持
虚拟终端序列在各个平台上都得到本机支持,使终端应用程序和命令行实用程序在操作系统的不同的版本和变体之间轻松移植(Windows 除外)。
与此相反,Windows 控制台 API 仅在 Windows 上受支持。 当尝试从一个平台或另一个平台移植命令行实用程序时,必须在 Windows 和虚拟终端之间编写扩展适配器或翻译库,反之亦然。
远程访问
虚拟终端序列具有远程访问的主要优势。 它们无需额外的工作来传输,或对设置标准远程命令行连接所需的操作执行远程过程调用。 只需通过管道、套接字、文件、串行端口或任何其他设备连接出站和入站传输通道(或单个双向通道),就足以完全携带应用程序将这些序列传递给远程主机所需的所有信息。
相反,只能在本地计算机上访问 Windows 控制台 API,所有远程操作都需要构建完整的远程调用和传输接口层,而不仅仅是一个简单的通道。
分离关注点
某些 Windows 控制台 API 提供对交互式命令行的输入和输出缓冲区或便捷功能的低级访问。 这可能包括在控制台子系统和主机环境内(而不是在命令行客户端应用程序本身中)编程的别名和命令历史记录。
与此相反,其他平台将存储应用程序的当前状态和便捷功能的任务交给命令行实用程序或 shell 本身。
Windows 控制台在控制台主机和 API 中处理此责任的方式使你可以更快、更轻松地编写包含这些功能的命令行应用程序,从而免除了记住绘制状态或处理编辑便捷功能的责任。 但是,由于实现和可用性的差异,几乎不可能跨平台、版本或方案远程连接这些活动。 这种处理责任的方式也使得这些 Windows 命令行应用程序的最终交互体验完全依赖于控制台主机的实现、优先级和发布周期。
例如,仅当命令行应用程序自行处理编辑问题时,才可以使用高级行编辑功能,如语法突出显示和复杂选择。 控制台永远不会有足够的上下文来像客户机应用程序那样以广泛的方式完全理解这些方案。
与此相反,其他平台使用虚拟终端序列通过可重用的客户端库(如 readline 和 ncurses)来处理这些活动和虚拟终端通信本身。 最终终端只负责显示信息并通过该双向通信通道接收输入。
错误方向谓词
使用 Windows 控制台,可以在输入和输出流上以与自然相反的方向执行一些操作。 这样,Windows 命令行应用程序便可以避免管理自己的缓冲区。 它还使 Windows 命令行应用可以执行高级操作,例如代表用户模拟/注入输入,或读回写入内容的部分历史记录。
虽然这为在单台计算机上的特定用户上下文中运行的 Windows 应用程序提供了额外的功能,但是当在某些情况下使用时,它还提供跨安全性和特权级别或域的矢量。 这种情况包括在同一台计算机上的上下文之间进行操作,或者跨上下文到另一台计算机或环境进行操作。
其他使用虚拟终端序列的平台不允许此活动。 我们建议从经典 Windows 控制台转换到虚拟终端序列的目的是为了在互操作性和安全性两方面与此策略融合。
直接窗口访问
Windows 控制台 API 图面为宿主窗口提供确切的窗口句柄。 这样一来,命令行实用程序就可以通过访问针对窗口句柄所允许的 Win32 API 的广泛范围来执行高级窗口操作。 这些 Win32 API 可以操控窗口状态、框架、图标或窗口的其他属性。
与此相反,在具有虚拟终端序列的其他平台上,可以对窗口执行的命令很有限。 这些命令可以执行诸如更改窗口大小或显示标题之类的操作,但它们必须与流的其余部分在相同的带和相同的控件下执行。
随着 Windows 的发展,对窗口句柄的安全控制和限制也有所增加。 此外,在任何特定的用户界面元素上的应用程序可寻址窗口句柄的性质和存在都得到了发展,特别是随着对设备外形规格和平台支持的增加。 随着平台和体验的发展,对命令行应用程序的直接窗口访问变得脆弱。
Unicode
UTF-8 是几乎所有新式平台上的 Unicode 数据都可接受的编码,因为它在可移植性、存储大小和处理时间之间取得了适当的平衡。 但是,Windows 以前选择 UTF-16 作为 Unicode 数据的主要编码。 Windows 中对 UTF-8 的支持有所增加,使用这些 Unicode 格式不会阻止使用其他编码。
Windows 控制台平台已支持并将继续支持所有现有代码页和编码。 如果需要,请使用 UTF-16 实现跨 Windows 版本的最大兼容性,并使用 UTF-8 执行算法转换。 控制台系统对 UTF-8 的支持正在增加。
可以通过所有控制台 API 的 W 变体,无需额外配置即可使用控制台中的 UTF-16 支持,并且对于通过与其他 Microsoft 和 Windows 平台功能和产品的 wchar_t
和 W 变体进行通信从而已精通 UTF-16 的应用程序来说,这是更有可能选择的方式。
根据需要,在使用 SetConsoleOutputCP 和 SetConsoleCP 方法将代码页设置为 65001
或 CP_UTF8
之后,可以通过针对控制台句柄的控制台 API 的 A 变体来使用控制台中的 UTF-8 支持。 仅当计算机未在“控制面板”的“区域”部分的非 Unicode 应用程序设置中选择“将 Unicode UTF-8 用于全球语言支持”时,才需要提前设置代码页。
注意
目前,通过 WriteConsole 和 WriteFile 方法在标准输出流上完全支持 UTF-8。 对输入流的支持因输入模式而异,并且会随着时间的推移而不断改进。 值得注意的是,默认的“cooked”输入模式尚不完全支持 UTF-8。 可在 GitHub 上的 microsoft/terminal#7777 中找到此工作的当前状态。 解决方法是使用算法可翻译的 UTF-16 通过 ReadConsoleW 或 ReadConsoleInputW 读取输入,直到未解决的问题得到解决。
建议
对于 Windows 上新的和正在进行的开发,建议使用虚拟终端序列作为与终端交互的方式。 这将使 Windows 命令行客户端应用程序与所有其他平台上的应用程序编程风格融合在一起。
使用 Windows 控制台 API 的例外情况
建立初始环境仍需要 Windows 控制台 API 的有限子集。 Windows 平台在进程、信号、设备和编码处理方面仍然与其他平台不同:
进程的标准句柄仍将由 GetStdHandle 和 SetStdHandle 控制。
在用于选择启用虚拟终端顺序支持的句柄上配置控制台模式将通过 GetConsoleMode 和 SetConsoleMode 进行处理。
通过 SetConsoleOutputCP 和 SetConsoleCP 方法声明代码页或 UTF-8 支持。
使用 AllocConsole、AttachConsole 和 FreeConsole 时,可能需要某个级别的总体进程管理才能加入或退出控制台设备会话。
信号和信号处理将继续通过 SetConsoleCtrlHandler、HandlerRoutine 和 GenerateConsoleCtrlEvent 进行。
可通过 WriteConsole 和 ReadConsole 来与控制台设备句柄通信。 这些也可以通过编程语言运行时以:-C 运行时 (CRT) 的形式加以利用:流 I/O,如 printf、scanf、putc、getc 或其他级别的 I/O 函数。 - C++ 标准库 (STL):iostream,如 cout 和 cin。 - .NET 运行时:System.Console,如 Console.WriteLine。
必须了解窗口大小更改的应用程序仍将需要使用 ReadConsoleInput 接收与键事件交错的信息,因为仅 ReadConsole 会将其丢弃。
对于试图绘制列、网格或填充显示器的应用程序,仍必须使用 GetConsoleScreenBufferInfo 来查找窗口大小。 窗口和缓冲区大小将在伪控制台会话中进行匹配。
未来规划和伪控制台
目前还没有从平台中删除 Windows 控制台 API 的计划。
相反,Windows 控制台主机提供了伪控制台技术,可将现有的 Windows 命令行应用程序调用转换为虚拟终端序列,并远程或跨平台将它们转发给其他托管环境。
这种转换并不完美。 它需要控制台主机窗口来维护 Windows 向用户显示的内容的模拟环境。 然后,它将此模拟环境的副本投影到伪控制台主机。 所有 Windows 控制台 API 调用都在模拟环境中运行,以满足旧版命令行客户端应用程序的需求。 仅会将效果传播到最终主机上。
因此,建议在 Windows 和其他平台实现跨平台完全兼容并完全支持所有新功能和方案的命令行应用程序,以移动到虚拟终端序列,并调整命令行应用程序的体系结构以使其与所有平台保持一致。
有关命令行应用程序的此 Windows 转换的详细信息,请参阅我们的生态系统路线图。