首页
/ 在 Windows Terminal 与 conhost 源码中用好 WIL:unique_handle、make_unique_nothrow 与 RETURN_IF_* 错误处理模式

在 Windows Terminal 与 conhost 源码中用好 WIL:unique_handle、make_unique_nothrow 与 RETURN_IF_* 错误处理模式

2026-09-06 11:07:31作者:卓艾滢Kingsley

doc/WIL.md 是 OpenConsole 仓库(Windows Terminal 与原生控制台宿主 conhost 的同一代码库)中关于 Windows Implementation Library(WIL)的内部使用指南。WIL 是一个 header-only 库,目标是让 Windows API 的调用更可预测、更少出 bug。读完本篇,你将掌握三个在 conhost 与 Windows Terminal 源码中被大量使用的 WIL 能力:用 wil::unique_handle 管理 OS 资源、用 wil::make_unique_nothrow() 做无异常内存分配、以及用 RETURN_IF_* 宏族统一处理 HRESULT/Win32 错误并把失败自动记录到项目的全局追踪通道。

1. WIL 命名空间约定:wil::wistd::

WIL 的大部分函数位于 wil::wistd:: 两个命名空间中:

  • wistd:: 命名空间:放置那些在 STL 的 std:: 中有等价物、但带有特殊能力的组件,最典型的就是**无异常(exception-free)**版本,例如 wistd::unique_ptr。这一点与控制台代码库高度契合——conhost 的核心路径传统上不依赖 C++ 异常;
  • wil:: 命名空间:其余所有内容都放在这里。

在仓库中,WIL 头文件被广泛包含,从 Windows Terminal 各组件(如 TerminalApp 的预编译头WindowsTerminal 的预编译头)到 conhost 服务端(server 入口)、WinConPty(winconpty.cpp)乃至小工具(buffersizeechokey)都能看到 #include <wil/...>,是贯穿整个代码库的统一基础设施。

2. 智能指针:让 Windows 资源离开作用域即释放

2.1 wil::unique_handle:自动调用正确的 Close 函数

wil/resource.h 中提供了一组面向 Windows 系统资源的“类智能指针”:文件句柄、套接字句柄、进程句柄等等。它们以 wil::unique_handle 的形式使用,在离开作用域时会自动调用与资源类型匹配的正确 OS 关闭函数(比如文件/进程句柄对应 CloseHandle())。

WinConPty 模块的 ConPtyCreatePseudoConsole 流程是文档所述模式的教科书式实例。winconpty.cpp 中,函数一开头就声明好后续所有步骤可能用到的句柄:

// src/winconpty/winconpty.cpp
wil::unique_handle serverHandle;
wil::unique_handle referenceHandle;
...
wil::unique_handle signalPipeConhostSide;
wil::unique_handle signalPipeOurSide;

随后对 CreatePipeDeviceIoControl 等每一步 API 调用都用错误检查宏包裹;一旦任何一步失败,函数提前返回,而上面声明的所有句柄会在栈展开/正常返回时由 wil::unique_handle 析构时自动关闭,不存在“漏关某一路管道”的分支风险。

同样的写法遍布 conhost 的进程间通信初始化代码 srvinit.cpp

wil::unique_handle signalPipeTheirSide;
wil::unique_handle signalPipeOurSide;

以及 srvinit.cpp 中控制台输入/输出管道的四路句柄 inPipeOurSide/outPipeOurSide 等。Windows Terminal 侧同样如此,例如 Entrypoints.cppwil::unique_handle ServerHandle;ReferenceHandle;ClientHandle[3];,以及线程句柄成员 AzureConnection.hConptyConnection.h 中的 wil::unique_handle _hOutputThread;

2.2 局部作用域写法:打开即用、即用完即释放

wil::unique_handle 也可以在声明处直接构造,让资源的生命周期严格限定在一个语句块内:

// src/cascadia/TerminalApp/MarkdownPaneContent.cpp
const wil::unique_handle file{ CreateFileW(
    _filePath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE,
    nullptr, OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, nullptr) };

MarkdownPaneContent.cppControlCore.cpp 都用这种单行写法读取 markdown 文件;types/utils.cppwil::unique_handle processToken{ GetCurrentProcessToken() }; 则是 token 句柄的典型用法——获取 token 后不需要手动 CloseHandle

2.3 自定义删除器:句柄之外的资源也能管

wil::unique_handle 针对 HANDLE,但对非 HANDLE 的 OS 资源,WIL 的 RAII 模板支持自定义删除器。til/regex.h 用它包装 ICU 的正则句柄:

// src/inc/til/regex.h
using unique_uregex = wistd::unique_ptr<URegularExpression,
    wil::function_deleter<decltype(&uregex_close), &uregex_close>>;

即:用 wistd::unique_ptr + wil::function_deleter 组合,把 uregex_close 作为析构时的清理函数——这正是“智能指针 + 匹配清理函数”模式在非 handle 资源上的通用化。

2.4 wil::make_unique_nothrow():无异常的 std::make_unique

文档指出,wil::make_unique_nothrow()std::make_unique 类似,但不抛出异常,返回 wistd::unique_ptr(而非 std::unique_ptr),便于与控制台既有的无异常代码集成。

仓库中的真实用例包括 ft_host/API_InputTests.cpp

auto irBuffer = wil::make_unique_nothrow<INPUT_RECORD[]>(cBuffer);
auto buf      = wil::make_unique_nothrow<wchar_t[]>(buflen);

以及 ConPty 的创建流程

wil::unique_handle duplicatedInput;
wil::unique_handle duplicatedOutput;
RETURN_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(), hInput, GetCurrentProcess(), duplicatedInput.addressof(), 0, TRUE, DUPLICATE_SAME_ACCESS));
RETURN_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(), hOutput, GetCurrentProcess(), duplicatedOutput.addressof(), 0, TRUE, DUPLICATE_SAME_ACCESS));

这里同时展示了 wistd::unique_ptrwil::unique_handle.addressof() 访问器:把指针的存储地址作为出参传给 Win32 API,失败分支则交给下文的 RETURN_IF_* 宏处理。

3. 结果码处理:RETURN_IF_* 宏族

3.1 动机:DuplicateHandle() + GetLastError() 的样板问题

Windows API 有五花八门的失败表示方式。以 DuplicateHandle() 为例:它返回 BOOL,失败时为 FALSE,调用者需要再去 GetLastError() 拿真实的错误码。文档给出的解法是:用宏 RETURN_IF_WIN32_BOOL_FALSE 包裹这个调用,它会自动处理这个模式——失败时自动抓取 Win32 错误并转换为等价的 HRESULT 从当前函数返回,成功则原样继续执行。上面 2.4 节的代码就是该宏的完整实战样本。

3.2 组合出“资源零泄漏”的函数骨架

文档总结的核心模式是:

  1. 在函数开头用 std::unique_ptrwistd::unique_ptr 或各类 wil:: 智能指针/智能句柄声明好全部所需资源;
  2. 对每一次 Windows API 调用套上对应的 RETURN_IF_*
  3. 由此可以保证在任何失败路径下资源都会被正确清理

该模式有一个前提约束:函数通常需要以 HRESULT 作为返回值,数据结果通过出参(out pointer 参数)返回。文档同时提示存在例外情况,细节需阅读 WIL 头文件本身。

在 conhost 中这一模式的规模可观:仅 server/ApiDispatchers.cpp 一个文件里就有上百处 RETURN_IF_* 调用,host/propslib 的 DelegationConfig.cppGDI 渲染器 等也都是同一写法,说明它不是个别模块的习惯,而是整个代码库的错误处理基线。

3.3 LOG_IF_*:只记录、不中断

如果某处失败你只是想记录一下用于调试、但让程序继续跑,WIL 提供了与 RETURN_IF_* 一一对应的 LOG_IF_* 版本:记录失败后“keep rolling”。仓库中两者都有大量使用,例如 host/settings.cppTerminalConnection/ConptyConnection.cpp 等文件里可以看到 LOG_IF_* 系列宏的调用。

4. 失败自动入日志:项目对 WIL 遥测钩子的定制

文档特别提到:采用上述模式后,“任何时点的失败都会被记录到我们的全局追踪/调试通道,可在调试器输出中查看到精确的行号与函数信息”。仓库里这条链路的落地实现在 inc/WilErrorReporting.h

  • ReportFailureToFallbackProvider()第 16–58 行)接收 WIL 回调传来的 wil::FailureInfo,通过 TraceLoggingWritehresult、源文件名 pszFile、行号 uLineNumber、模块名 pszModule、失败类型、自定义消息、线程 ID 等 14 个字段写入 FallbackError ETW 事件,便于在调试输出/遥测分析中直接定位到出错的代码行;
  • 其中有一个精心处理的边界:HRESULT 0x80131515 是 XAML 无障碍代码要求必须“抛出”的伪错误(见 WilErrorReporting.h 第 19–27 行的注释),并非真实故障且极其高频,因此该回调直接跳过、不记录,避免日志噪音;
  • EnableFallbackFailureReporting(provider)第 60–69 行)把该回调注册为 WIL 的结果遥测后备:::wil::SetResultTelemetryFallback(...),之后凡是 WIL 宏记录/返回的失败都会流经这条通道。

各模块在初始化时传入自己的 ETW provider 完成接线,例如 WindowsTerminal/main.cpp

::Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hWindowsTerminalProvider);

TerminalApp/init.cppTerminalControl/init.cppTerminalSettingsModel/init.cpp 等均遵循同一约定。

5. 小结:在本仓库中复用这套模式的要点

  • 管理 HANDLE 类资源优先 wil::unique_handle(成员或局部作用域均可),离开作用域自动 CloseHandle();非 HANDLE 资源用 wistd::unique_ptr + wil::function_deleter 指定清理函数(参照 til/regex.h);
  • 无异常代码路径中做堆分配用 wil::make_unique_nothrow<T>(...),得到 wistd::unique_ptr
  • 需要 BOOL + GetLastError() 模式的调用用 RETURN_IF_WIN32_BOOL_FALSE(如 DuplicateHandle),其余 API 按返回码类型选择对应 RETURN_IF_*;不想中断执行时改用 LOG_IF_*
  • 函数签名适配该模式:返回 HRESULT、结果经出参返回;失败信息由 WIL 自动携带文件与行号进入项目的 FallbackError 追踪事件,排查时无需逐处加日志。

适用前提:WIL 是 Windows 平台的 header-only 库,以上模式仅适用于该代码库中面向 Windows API 的 C++ 代码;具体宏的完整列表与边界情况以 WIL 自身头文件为准。

登录后查看全文
热门项目推荐
相关项目推荐