Windows Terminal 标签页拆离与默认终端 IPC 机制:从 #1256 设计文档到 conhost/wt 协作架构
【免费下载链接】terminalThe new Windows Terminal and the original Windows console host, all in the same place!项目地址: https://gitcode.com/GitHub_Trending/term/terminal
本文围绕 Windows Terminal 仓库中的规格草稿 Tab Tearoff 设计文档 展开,系统讲解支持"标签页拆离/合并(tab tearoff/merge)"与"默认终端应用接管(default app)"两大特性所需的跨进程通信(IPC)设计:从 Manager 仲裁进程、内核句柄的三种传递模式,到 OLE 拖放、协议处理器与 conhost.exe 降级回退策略。读完本文,你能理解 Windows Terminal 与 conhost 之间"会话如何跨进程移交"的完整设计脉络,并对照当前源码看到这些设计思想在wt.exe内部以何种方式落地(如 TabView 拖放数据载荷、Monarch 窗口仲裁、拆离窗口的_contentBounds定位)。
一、设计目标与两大核心驱动力
该规格由 Michael Niksa 于 2019 年提出(issue #1256),核心是描述支持以下能力所需的进程间通信机制:
- 标签页拆离与合并:把一个标签页从一个
wt.exe实例拖出,合并到另一个wt.exe实例中; - 默认应用接管:命令行应用的启动能够触发一个托管环境,而不是系统内置(in-box)的
conhost.exe。
作者的结论是:这两类需求都要求存在一个进程间通信管理器(IPC manager),能够收发代表"客户端应用 ↔ 托管环境"连接的系统句柄。作者在 2019 年 7 月微软黑客松期间用一条分支做了多进程管道服务器原型(分支链接已不存在于当前仓库,原文仅记录其存在),并发现"问题比答案多",因此该文档定位为一份机制探索性的设计规格,而非完整实现。
二、公共组件:Manager 与连接细节
2.1 Manager(仲裁管理器)
文档提出的 Manager 形态:
- 需要一段"服务器/管理器"代码,等待来自各
wt.exe进程(乃至conhost.exe进程)的连接,在进程之间仲裁(broker)连接; - 它可以运行在独立进程中,也可以由某个被选为"主管理器(primary manager)"的
wt.exe承担; - 创建时应建立通信通道和全局互斥量(global mutex)。后续启动的其他
wt.exe检测到主管理器存在后应等待该 mutex;主进程消失后,操作系统调度器会唤醒其中一个等待者,它拿到锁并接管主管理通道——这是一种**可恢复(resilient)**的选主方案; - 备选方案是让管理器完全独立,且要求所有
wt.exe必须保持连接,一旦任一进程与管理器断开,全部关闭。作者明确倾向于前一种可恢复方案,但承认浏览器类应用选择后者的做法必有其原因。
实现路径上,作者的原型采用了Message 类型的多线程管道服务器(Multithreaded Pipe Server),但文档的正式结论是:最终应"形式化地围绕一个更结构化、经过测试、天然具备安全性的机制",即COM 服务器接口。
2.2 连接细节:可传递的两类信息
任何一次连接传递本质上只有两种能力:
- 在两个进程之间传递内核句柄;
- 传递任意长度的结构化信息(路径、设置等)。
标签页拆离与默认应用两个场景都需要这两种能力。
三种 Fresh Start(全新启动)模式
对即将被新启动的应用,开启会话所需的信息是以下三者之一:
- 服务器(及引用)句柄:描述控制台服务器与命令行客户端进程之间驱动连接的句柄。
conhost.exe可以将其包装成 PTY;其中还可能包含 LNK(快捷方式)偏好; - 命令行字符串 + 工作目录:描述要启动哪个命令行客户端。
conhost.exe启动它并在过程中创建服务器/引用句柄,再将其转为 PTY; - 已存在的 PTY 会话:带 read、write、signal 三个句柄。
传递连接时必须意识到这三种模式并逐一中继给目标wt.exe。
系统句柄的跨进程传递:OpenProcess + DuplicateHandle
对系统句柄,方案是:通过 Manager 向目标进程发起请求,取得其PID再告知源进程;源进程用PID配合OpenProcess(以PROCESS_DUP_HANDLE权限)打开目标进程,随后用DuplicateHandle把上述句柄复制进目标进程。文档特别指出:打开与复制句柄本身就需要操作系统检查我们的访问令牌(access token)与权限,这天然承担了相当一部分安全检查。
命令行字符串与工作目录
直接把命令行字符串和工作目录传给目标wt.exe,让它像"用户从下拉菜单选择了一个选项"一样正常启动一个新 ConPTY。一个细节技巧:可能需要把命令行字符串与用户 profile 做匹配,以对齐图标与会话启动偏好。
LNK 快捷方式的迁移
从 LNK 启动的应用,用户可能期望:即使快捷方式的属性在技术上属于conhost.exe偏好而非wt.exe偏好,在wt.exe里打开的窗口仍应套用这些属性。文档给出的行为是:把 LNK 文件信息像命令行字符串一样传给wt.exe,由它使用共享库的快捷方式解析逻辑提取信息并迁移为Settings偏好;是否持久化该偏好供以后在下拉菜单中使用,可以做成选项或询问。
Already Running(会话已在运行)的迁移
对已在运行的应用,成功迁移到新标签页位置需要发送三类信息:
- ConPTY 的 read、write、signal 句柄;
- 回滚历史(scroll-back history)——它保存在
wt.exe内部,并不是 PTY 模式下conhost.exe任何时刻重绘内容的组成部分; - 与
Settings相关的用户偏好与会话信息。
把这些通过 IPC 机制发给目标端后,目标端按与源端标签页完全相同的参数立起新标签页。
文档还给出了一个替代方向:如果未来走向隔离进程模型——每个标签页/窗格有独立进程,UI 托管在另一个 frame/shell 进程中,外加一个 manager 进程——那么本就需要设计"UI 可被远程托管到其他界面(Component UI)"的方案;此时活跃会话的迁移只需要把该标签页/窗格的绘制与输入目标重定向到另一个 shell,这可能比把"构成一个会话的所有部件"整体搬过去再重建更简单、更可靠。
三、标签页拆离的实现路径
文档在"Common Pieces"之外,针对拆离场景单独描述:
- 给标签栏的 on-drag 增加处理器,并很可能要实现拖放处理器;
- 由于拖放处理器基于 OLE(COM),这是"整个 Manager 都应实现为 COM"的又一个理由(作者坦承此处是低认知度下的理论设计,需要探索);
- WinUI 的 TabView 控件预期会支持用自身拖放重排标签页,但拖拽开始时我们大概率需要创建一个携带会话 GUID 的拖拽源(drag source);
- 随后交给操作系统处理 drop:若 drop 落在另一个
wt.exe上,就用 drop 载荷中的会话 GUID 在进程间传递连接信息;若落在别处,拖拽源应能感知这一点,并拉起一个新的wt.exe,其启动参数指定"启动后向 Manager 执行针对该会话 GUID 的 drop 部分",而不是打开默认标签页。
四、默认应用:conhost 的移交与回退
4.1 基本流程与失败回退
默认应用启动时,conhost.exe必须尝试把传入连接移交给已注册的终端处理器,而不是自己弹出 UI 窗口。若注册处理器启动失败、不存在注册处理器、或该机制任何环节失败,conhost.exe需要回退到开发前的行为(该弹窗就弹窗、该隐藏就隐藏)——这是文档中反复强调的可靠性底线。
4.2 交互式 vs 非交互式检测
必须区分两种模式:
- 交互式:终端用户希望以可见窗口启动命令行应用,以查看输出、输入内容;
- 非交互式:工具、实用程序、服务在无可视窗口(可能带重定向句柄)的情况下启动命令行应用。
编译器、脚本、工具随时在后台调用命令行工具,我们不希望捕获非交互式会话——它们不需要输出与显示,没必要承担转入终端的开销。此外还要识别正在启动的 ConPTY,避免"移交—再被识别—再移交"的无限循环。
最大的难点是:在开始接受来自服务器句柄的连接之前,我们不知道它是否为交互式。文档给出两条处理路线:
路线 A:系统自带 conhost 处理
系统自带的conhost.exe直接接受来自服务器句柄的连接,确认某个wt.exe可以接管 UI 托管后,把自己切换为ConPTY 模式,把句柄交给wt.exe,自己以 PTY 模式在后台保持不可见(与wt.exe自己启动该连接时的情形一样)。
- 优点:大部分启动连接流程照常发生;拿到服务器句柄的那个
conhost.exe将负责服务该客户端会话的整个生命周期,可以完全不用担心驱动如何反应、应用如何折腾进程间关系——一切都"正常"。 - 缺点:从快捷方式、资源管理器、运行框启动命令行应用(正是触发默认应用场景的入口)将使用的是旧版本的 PTY。若希望使用应用包内已有改进的 PTY,就必须把服务器连接迁移进包内 PTY,由此引出额外难题:
- 可能要让原
conhost.exe保持打开直到另一个进程退出(防止有人正在等待该进程); - 要么由包内被委派 PTY 的 conhost 在启动连接时自行判定交互性,要么由外部 conhost 先启动连接、发现是交互式后中途移交——"或者类似的某种舞蹈(dance)"。
- 可能要让原
路线 B:包内 conhost 处理
把 System32 中conhost.exe的服务器连接直接送进包内版本,由它处理:连上 broker,传递服务器句柄,让wt.exe用该特定服务器句柄创建一个 PTY 模式的conhost.exe。其优缺点与路线 A 正好相反,文档不再赘述。
4.3 向下兼容:让默认应用在旧版 OS 上工作
文档研究了三条路径并逐一评估:
- 安装时替换 system32 中的 conhost.exe:操作系统(经由
kernelbase.dll中的ConsoleInitialize)会启动C:\windows\system32\conhost.exe来开启默认应用会话。技术上可以把一个名为conhost.exe的OpenConsole.exe放进 system32 让新代码跑在旧 OS 上(前提是装好 CRT、链接 in-OS-CRT、对所有旧版本不可用的 API/引用做条件特性检测)。作者说明这实际上就是内部在没有完整 nightly build 时的本地开发测试方式,"在某种程度上被证明可行"。但问题在于:OS 会主动通过 ACL 防御对 system32 二进制的改动(Windows File Protection 时代已远去,但防御仍在);内部能成是因为测试证书链;正式的应用签名证书是否会被像 OS 签名证书一样信任也不确定;且无法在 Windows 之外针对 in-box CRT 构建,只能附带 MSVCRT redist——"也很恶心"。 - 更新 kernelbase.dll 以查询启动偏好/协议处理器启动控制台宿主:要覆盖到最新 OS 之外的版本就必须对 kernelbase 做向后服务(service)。而
kernelbase.dll对"一切"都至关重要,几乎没有任何可能获批向后服务以添加特性;即便为即将发布的版本改动它风险也极高。"思想实验到此结束。" - 更新 conhost.exe 本身去查询启动偏好/协议处理器启动另一个控制台宿主:让 system32 的
conhost.exe把会话委派给一个(期望更新的)conhost.exe。由于内置驱动协议不改变、也无意改变,前后兼容故事很好;若被委派的conhost.exe启动失败,还可以像改动前一样回退启动旧的。向后多个版本服务 conhost.exe 仍是有难度的论证(资源是否应投入被放弃的 OS 版本),但远小于颠覆整个kernelbase.dll。文档补充:协议处理器在 Windows 上是广为人知且相对完善/测试充分的机制,新旧应用都能处理协议,协议处理器还能携带参数,"不需要依赖其他团队改变 OS 其余部分的运作方式"。
4.4 启动参数的传递方式
文档列出三个选项:
conhost.exe查询wt.exe的包注册并带参调用入口点;可扩展为查询"注册为默认的那个包"以支持第三方宿主——但要在 OS 里内置选择机制,或用某种公开文档化的注册表键,"有点粗";conhost.exe调用**执行别名(execution alias)**并带参数——WSL 发行版启动器就是这么做的;- 为这类连接定义协议处理器,让
wt.exe注册它。协议处理器在 Windows 经典应用与打包/现代应用中都受良好支持,且必然具备传递某种参数数据的机制。这是作者最倾向的路线,形式如ms-term://incoming/<session-id>。接收方wt.exe联系管理器进程(若自己是第一个则把它建立起来),协商接收被指定的会话并在新标签页中打开。
五、UI/UX 设计
5.1 标签页拆离
理想世界(Ideal World)——体验应像浏览器应用:
- 鼠标按下并拖动标签页时应提供视觉上的拖拽提示;
- 左右拖动应在标签栏上给出重排的视觉指示,且不涉及 IPC 管理器服务;
- 上下拖动脱离标签栏时,应启动一个新的
wt.exe实例,把拖拽中标签页的状态作为初始启动点传入(忽略其他默认启动行为),并把拖拽/鼠标按下继续传给该新实例追随鼠标; - 继续把这个"游离标签页"拖到另一个运行中
wt.exe的标签栏上时,标签页与该实例合并;这个临时新建/游离帧的wt.exe在把最后一个标签页转出后关闭。
简化 V1(Simplified V1)——为首次迭代简化,转移不实时发生:
- 按下并拖动标签页时通过光标变化(之类的方式)提供拖拽视觉提示;
- 在释放之前不发生任何实际的标签页转移;
- 释放回同一
wt.exe实例的标签栏其他位置 → 在 TabView 控件内重排标签页; - 释放到另一个
wt.exe实例 → 通过 IPC 管理器中继通信通道与细节,在目标实例打开标签页,在源实例关闭该标签页; - 释放到任何非
wt.exe的地方 → 创建一个新的wt.exe实例,把连接作为默认启动参数传入。
文档还指出,如果能找到 Component UI 式方案(标签页/窗格有自己的进程,只把 UI/输入远程到 shell 中),随时更换承载某个元素的 shell/frame 宿主将变得"容易甚至平凡"。
5.2 默认应用的 UX
整体上应看起来完全像用户从快捷方式或启动磁贴启动了wt.exe,只是首个标签页不同于默认值:
- 无 WT 运行中:由系统触发来托管客户端应用的
conhost.exe找到已安装的wt.exe包并带参启动,用该连接替代默认标签页作为首个连接;conhost.exe不显示窗口,转入 ConPTY 模式,只有新的wt.exe及其标签页可见; - WT 已运行:
conhost.exe找到运行中的实例,用同样机制在标签栏末尾追加新标签页; - 多个 WT 运行中:
conhost.exe必须找到前台的、激活的或 primary/manager 的那个并把标签页发过去。文档承认作者不确定其他支持标签页的应用怎么做,"可以研究/学习"。
六、能力影响分析(Capabilities)
6.1 可访问性
作者认为该特性基本不改变可访问性。唯一要提出的是已知信息:UIA 框架基于PID、HWND 及其层级建立连接与部分逻辑推理;在标签页被挪动后"玩弄"这些元素,可能影响读屏应用在标签页洗牌后获取 UIA 树的能力。
6.2 安全
该特性必须经过安全评审/审计,文档列出几个明确关注点:
- mutex/pipe/通信必须限定在特定会话的特定用户内。另一用户在其会话中运行 WT 时,应使用完全独立的 manager 进程与系统对象;
- 可能必须抑制跨完整性级别(integrity level)的连接传递:高完整性进程天然有权对低完整性进程操作,即提升的
wt.exe理论上可以向标准级wt.exe发送标签页。可能需要禁止这种行为,甚至每个完整性级别一个管理器; - 涉及的每个内核对象需要什么样的 ACL/DACL/SACL 尚不清楚;
- 原型使用消息传递管道 + 自研协议——自研协议必须做 fuzzing,且作者承认"我肯定漏了什么"。多数安全顾虑若改用知名 IPC 机制(作者再次指向 COM 服务器)可能自然消除:比消息管道复杂,但安全性收益大,也免去了对协议做 fuzzing 的需要。
6.3 可靠性
简单实现会降低可靠性:在应用实例之间来回倒腾连接,默认情况下比不动更冒险,值得做的唯一理由就是用户体验。文档给出与 2.2 节替代方向呼应的缓解/增强路径:向浏览器式的进程/容器化模型再进一步,把每个标签页立为独立的进程宿主,wt.exe运行在多种模式下:
wt.exe - Manager Mode |- wt.exe - Frame Host Mode | |- wt.exe - Tab Host Mode | | |- conhost.exe - ConPTY mode | | |- pwsh.exe - Client application | |- wt.exe - Tab Host Mode | |- conhost.exe - ConPTY mode | |- cmd.exe - Client application |- wt.exe - Frame Host Mode |- wt.exe - Tab Host Mode |- conhost.exe - ConPTY mode |- pwsh.exe - Client application- Manager Mode:无 UI,作为 broker 坐在那里,持有给定窗口站/会话与完整性级别的内核对象,接受协议处理器例程,协助在标签页移动时于各 frame host 之间中继连接,并决定默认应用新标签页实例化在哪里;
- Frame Host Mode:单个标签页之外的完整外层外壳,托管标签栏、设置下拉、标题栏等;
- Tab Host Mode:单个标签页的内层外壳,含渲染区域、滚动条、输入等;
- Pane Host Mode:既然窗格已经成为现实,可能还要再深一层——或者它只是 Tab Host mode 的递归。
文档明确:这些进程之间如何连接"目前尚未探索"。
6.4 兼容性:进程树层级
文档指出核心兼容性担忧:客户端应用或外部工具判断"命令行客户端 ↔ 控制台宿主"关系的主要手段之一是进程树/层级,而本设计不可避免会让原本托管的conhost.exe(无论被wt.exe以 ConPTY 模式启动,还是 OS 响应无宿主命令行应用而启动)变成孤儿或与真正呈现它的 UI 脱离关联。
作者承认,可用 API 中可能可以复用"重排进程父子关系(把conhost.exe置为cmd.exe的父)"的机制——尽管cmd.exe通常先启动、kernelbase.dll的ConsoleInitialize例行程序先创建了conhost.exe——但这可能引入新问题:一个既有的例子是可访问性的 UIA 树不容忍父子关系洗牌,因为其通信通道会话常与 HWND/PID 的绑定关系挂钩。
两个终端之间(拆离/合并)的层级示例
初始状态:实例 A 中有cmd.exe与pwsh.exe两个标签页,实例 B 中有一个cmd.exe标签页:
- wt.exe (Terminal Instance A) |- conhost.exe (in PTY mode) - Hosted to A | |- cmd.exe |- conhost.exe (in PTY mode) - Hosted to A |- pwsh.exe <-- I will be dragged out - wt.exe (Terminal Instance B) |- conhost.exe (in PTY mode) - Hosted to B |- cmd.exepwsh.exe标签页从 A 撕下并 drop 到 B 之后,进程树实际上没有任何变化——连接细节、偏好与会话元数据经由 IPC 管理通道传递,但在外部观察者看来什么都没有变:
- wt.exe (Terminal Instance A) |- conhost.exe (in PTY mode) - Hosted to A | |- cmd.exe |- conhost.exe (in PTY mode) - Hosted to B |- pwsh.exe <-- I am hosted in B but I'm parented to A - wt.exe (Terminal Instance B) |- conhost.exe (in PTY mode) - Hosted to B |- cmd.exe文档断言:据作者所知,Windows 没有把应用重父子化(reparent)到另一个进程的机制。更有趣的是 A 死亡而 B 仍在运行时:
- conhost.exe (in PTY mode) - Hosted to B |- pwsh.exe <-- I am hosted in B but I'm parented to A - wt.exe (Terminal Instance B) |- conhost.exe (in PTY mode) - Hosted to B |- cmd.exe实例 A 死亡后,该conhost.exe继续运行,在 Process Explorer 等工具里呈现为顶层孤儿。文档给出的行动计划是:先实现能实现的,观察世界状态,再向前修正——我们并不确切知道有多少客户端应用会被这种"表观变化"影响;而且可能完全没问题,因为客户端应用始终父子挂在同一个conhost.exe之下,即使那些conhost.exe不再汇报给正确的wt.exe。另外,是否要允许外部工具发现这种层级,文档倾向于"没有强有力的理由就不提供"——理解本机进程层级是在未来扩展到远程连接时自我设限的好方法。
Conhost 与 Terminal 之间(默认应用)的层级示例
看起来很像上一节中 B 死亡后的情形:
- conhost.exe (in PTY mode) - Hosted to A |- pwsh.exe - wt.exe (Terminal Instance A)该conhost.exe是因无宿主的pwsh.exe启动而触发的,随后把自己转为 PTY 模式并接入wt.exe实例 A。
替代形态(利用 conhost 启动时的重父子化命令让树更整洁):
- conhost.exe - idling - wt.exe (Terminal Instance A) |- conhost.exe (in PTY mode) |- pwsh.exe顶层的conhost.exe响应无宿主的pwsh.exe而启动,识别到wt.exe在运行后把传入连接"摆渡"进该wt.exe;wt.exe在自己之下立起 PTY 模式的conhost.exe,其下是客户端pwsh.exe;PTY 模式的conhost.exe用启动时的 reparenting 命令把树整形成上图;而那个被"孤儿化"(最初启动的)conhost.exe会等待连接退出后才自行退出,以防有人正在等待它。
6.5 性能、功耗与效率
文档的判断很克制:这"显然比什么都不做低效",因为要拉起服务器、协议与处理器来倒腾东西;但只要线程与服务大部分时间在睡眠、仅在某个内核/系统事件时唤醒,就不会在功耗与后台资源上浪费太多。同时wt.exe在所有效率类别上都比单独的conhost.exe差——不仅显示"漂亮"要资源,还需要其下以 PTY 模式运行的conhost.exe来适配 API 调用;这通常可被更在意体验而非总性能的用户接受。但相较"用户直接选用wt.exe而非conhost.exe"这一情形,本特性不太可能再造成多大(甚至任何)额外损耗。
七、源码印证:文档思想在当前 wt.exe 中的落点
该文档成文于 2019 年,属于草稿规格;当前仓库中的wt.exe尚未实现文档构想的独立 Manager 进程与跨实例句柄移交,但文档中的若干机制已经在源码中以"进程内"的形式被验证和落地,可对照阅读:
7.1 拖拽标签页的 DataPackage 载荷:windowId + pid + Move
文档设想"创建携带会话标识的拖拽源,交给 OS 处理 drop"。当前实现正是用 WinUITabView的拖放事件链完成这一点(TerminalPage.cpp):
_onTabStripDragStarting:把被拖标签页存入_stashed.draggedTab,记录光标相对标签页原点的dragOffset(后续用于定位新窗口),并向拖拽数据写入windowId与当前进程pid两个属性,声明DataPackageOperation::Move;_onTabStripDragOver(TerminalPage.cpp):目标页必须确认数据载荷含windowId且pid等于本进程 PID,才接受 Move 操作——这与文档"drop 载荷携带标识、由源端完成信息传递"的思路一致,只是标识从"会话 GUID"具体化为"窗口 ID + PID",且当前限定了同进程(pid == GetCurrentProcessId()),跨wt.exe实例的合并尚依赖后续演进;_onTabStripDrop(TerminalPage.cpp):解包 DataPackage 得到来源窗口 ID,计算 drop 点落在哪个标签之间(落在标签左半则插入该位置),构造RequestReceiveContentArgs{src, targetWindowId, index},然后上抛给 Monarch——由单例AppLogic("the monarch")把请求再分发回源TerminalPage,源页执行SendContentToOther。这正是文档中"OS 处理 drop、源端被通知后发起移交"事件链的具体化,且由于所有TerminalPage都运行在同一wt.exe进程内,"IPC Manager"退化为进程内单例仲裁。
7.2 撕出到空白处:BuildStartupActions + 新窗口定位
文档 Simplified V1 的第三分支——"释放到非 wt.exe 的地方 → 新建实例并把连接作为默认启动参数传入"——在源码中对应 _sendDraggedTabToWindow:
- 目标窗口 ID 为
"-1"时表示"新建窗口"(见 _onTabDroppedOutside 的注释),指针位置减去dragOffset得到新窗口的期望位置,使被拖标签页基本仍停在光标正下方; - 移交内容通过
tab->BuildStartupActions(BuildStartupKind::Content)序列化为启动动作,随后_DetachTabFromWindow+_RemoveTab把标签页从源窗口摘除——即文档"Fresh Start 三模式"中"以结构化信息(而非裸句柄)描述会话"的思路:源窗口先按原参数重建等价会话(目标端),再释放源端,而非试图跨进程复制活句柄。
新窗口侧,TerminalWindow.cpp 用成员_contentBounds(TerminalWindow.h)记录拆离时传入的内容区域,在尺寸计算(DIP × scale)与初始布局中优先使用它,并据此判断"当前窗口是拆离(tear out)过程中打开的"。此外 TerminalWindow::Create 的注释明确指出:tear-out / reattach 情形下不能使用 settings 的startupActions(GH#16050),避免拆离重建的窗口被默认启动动作二次干扰——这恰好印证了文档中"新实例启动参数应指定执行 drop 部分而非启动默认标签页"的要求。
7.3 无障碍佐证:UIA 与拆离的耦合
文档 6.1 节担忧"UIA 基于 PID/HWND/层级"会被标签页挪动影响。当前源码中这一耦合是显式存在的:TermControlAutomationPeer.h 注释说明该 peer "based on InteractivityAutomationPeer, to support tab tear out"——可访问性 peer 需要理解拆离/重附加时控件宿主的变化,这与文档"洗牌 PID/HWND 层级可能影响读屏应用获取 UIA 树"的预警相互呼应。
7.4 默认应用侧:防止移交风暴
ConsoleArguments.cpp 中存在注释 "Prevent default application handoff to a different console/terminal",说明 conhost 侧确实存在"向不同控制台/终端移交"的参数处理逻辑,文档 4.2 节"识别正在启动的 ConPTY、防止无限循环移交"的顾虑在现行代码里是有对应物的。当前 conhost 的默认终端注册与移交细节另见仓库中 Default Terminal 规格 与 进程模型 2.0 规格,可视为本草稿规格在默认应用方向上的后续演进。
八、潜在问题清单
文档在 Potential Issues 一节汇总了最关键的四个风险(前文各节已分别展开):
- 进程树布局:层级中的进程对"目视用工具或程序化检查它的人"可能不合逻辑;
- 进程与内核对象生命周期:应用可能依赖特定进程或对象与其托管窗口的生命周期关系,而我们在应用 job object、挪动所有权让标签页生效的过程中可能动了这些;
- 默认启动预期:测试工具或自动化可能依赖
conhost.exe就是宿主应用,或尚未准备好容忍其他应用被拉起;作者认为交互/非交互检测能缓解,但仍需保持警惕; AttachConsole/DetachConsole/AllocConsole:作者直言"完全不知道这些 API 会怎样"。AttachConsole有基于进程层级的限制,在奇怪的父子顺序下可能表现"有趣",这或许是必须调整进程父子关系(或在底层改 API)的驱动因素;DetachConsole可能造成"标签页从终端里消失、job object 导致全部死亡"的问题;AttachConsole也不保证回到同一个wt.exe或任何wt.exe。
九、未来方向:扩展隔离
文档末尾指出,拆离与默认应用所需的进程容器化 + 跨进程通信模型若真的建成,同一套机制也许可以隔离扩展(extensions):扩展在长期路线图上存在,但对应用稳定性与完整性固有地有风险;浏览器式"每个标签页一个进程宿主"的容器化若能落地,扩展可以同样被装进容器里。这与 6.3 节的可靠性论证同出一源:浏览器用进程隔离同时换来了稳定性与能力扩展空间。
十、小结
这份 2019 年的规格文档是理解 Windows Terminal 跨实例/跨宿主会话模型的一份基础设计档案,其核心结论可以概括为:
- 机制选型:优先采用结构化、可测试、自带安全语义的知名 IPC 机制(COM 服务器、协议处理器),避免自研协议管道的安全与 fuzz 负担;
- 信息模型:会话传递 = 内核句柄(服务器/PTY read-write-signal,经
OpenProcess+DuplicateHandle跨进程复制)+ 结构化描述(命令行/工作目录/LNK 偏好/设置/回滚历史); - 可靠性底线:默认应用接管必须全链路可回退到 conhost 原行为;
- 演进方向:从单进程
wt.exe走向 Manager / Frame Host / Tab Host(/ Pane Host)多进程模型,顺带获得可靠性提升与扩展隔离能力。
对照当前源码可以看到:拖拽数据载荷、Monarch 仲裁、拆离窗口定位与"tear-out 不走 startupActions"等设计,都是该文档事件链思想在进程内场景下的先行落地;而真正的跨实例句柄移交与默认应用移交,仍在由后续规格(Default Terminal、Process Model 2.0)与 conhost 侧代码持续演进中。
【免费下载链接】terminalThe new Windows Terminal and the original Windows console host, all in the same place!项目地址: https://gitcode.com/GitHub_Trending/term/terminal
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考