Files
胡飞 fa6ca0d1b1 fix(windows): 宿主自嵌 Common-Controls v6 清单,修 v0.20.1 启动即死
v0.20.1 的 Windows 产物(zip / portable / MSI 里装的是同一个 navop.exe)启动即失败:
「navop.exe - 无法找到入口 / 无法定位程序输入点 TaskDialogIndirect 于动态链接库
…\AppData\Local\Temp\navop-update\staged-<pid>-<ms>\navop.exe 上」。加载器在 `main`
之前就以 STATUS_ENTRYPOINT_NOT_FOUND(0xC0000139) 退出,所以更新表现成「什么都没发生」。

根因不是缺 comctl32.dll,而是产物**没有任何应用程序清单**:

- gpui 的 Windows 层在 `PlatformWindow::prompt` 中静态导入 `comctl32!TaskDialogIndirect`
  (实测 v0.20.1 的 navop.exe:IMPORT 表里有该符号、DELAY_IMPORT 目录为空 ⇒ load-time 导入);
- 该导出只存在于 Common-Controls v6 的 SxS 程序集,只有 exe 自己的清单声明了
  `Microsoft.Windows.Common-Controls 6.0.0.0` 才会激活 v6;没有清单就挂 System32 里的
  5.82(不导出该符号)⇒ 加载器失败;
- 实测 v0.20.1 的 navop.exe 全文件搜 `Common-Controls` / `<assembly` /
  `requestedExecutionLevel` 零命中 ⇒ 清单确实没被嵌进去。

清单的责任方在 v0.20.1 变了:`gpui-fast 0.1.8` 的 build.rs 是
`embed_resource_unless_host_provides_it()`,而 gpui-kit 声明了
`windows-manifest-provided-by-host`(`cargo tree -e features -p main -i gpui-fast` 可核)。
Cargo 特性只做并集、下游关不掉 ⇒ 这份清单只能由宿主 navop 自己提供。
v0.20.0 走旧门面 `gpui-pre`,它是无条件 embed 的,所以此前没暴露。

- `resources/windows/navop.manifest.xml`(新增):宿主清单,与上游 Zed 的
  `crates/gpui/resources/windows/gpui.manifest.xml` 对齐(asInvoker + supportedOS Win10 +
  Common-Controls 6.0.0.0 + `dpiAware true/pm` / `dpiAwareness PerMonitorV2` /
  `heapType SegmentHeap`)。DPI 与堆这一段刻意与 v0.20.0 保持一致:gpui 两代都**不**在运行时
  调 `SetProcessDpiAwareness`(全仓 grep 无命中),清单是 DPI 感知的唯一来源,漏掉会退回
  DPI-unaware(高 DPI 下整窗被系统位图拉伸)。文件不带 XML 声明:winresource 是把清单按 RC
  字符串逐行写成 `" <line> "`,声明前出现空白不是合法的 XML prolog。
- `main/build.rs`:加 `set_version_info(VersionInfo::FILETYPE, 1)` +
  `set_manifest(include_str!("../resources/windows/navop.manifest.xml"))`。前者不是可选项 ——
  winresource 只在 `VersionInfo::FILETYPE` 存在时才写清单块(`winresource-0.1.31/lib.rs:578`),
  漏了会**静默不嵌清单**。用 `set_manifest` 而非 `set_manifest_file`,避开 rc 的路径转义。
- `script/validate-windows-exe.mjs`(新增):PE 层门禁,解析节表定位 `.rsrc` 并在其原始区间内查
  三条必需串,另外判别「静态导入 TaskDialogIndirect 却无清单」这一真实故障组合;失败退出码 1
  并打印含 `0xC0000139` 的修法。判据**必须同时搜 ASCII 与 UTF-16LE**:实测 llvm-rc 对纯 ASCII
  清单写的是 8 位,MSVC rc.exe 才写 UTF-16,只搜一种编码必漏。
- `.github/workflows/release.yml`:Windows 腿在 `Build release binary (Windows)` 之后、
  `Package (Windows)` 之前插入 `Validate Windows executable manifest`。这段代码在 macOS/Linux 上
  根本不参与编译,而「清单没嵌进去」在编译期没有任何信号,只能在真实产物上校验。
- `script/test-release-packaging.mjs`:加合成 PE fixture(`peFixtureWithResource`)与两个用例
  —— 断言清单内容、`main/build.rs` 的三处调用、workflow 步骤的存在与顺序;以及门禁对
  正样本 / 负样本 / 非 PE 三种输入的退出码与报错内容。

验证(本机为 macOS,无法编译 Windows target):

- `node --test script/test-release-packaging.mjs` = 33 passed / 0 failed
- `cfg(target_os = "windows")` 块在 macOS 上不参与编译,`cargo check` 给不了信号。因此建探针
  工程把 `main/build.rs` **逐字复制、只删掉那行 `#[cfg]`**,配
  `[build-dependencies] winresource = "0.1"` ⇒ 该块**编译通过**(`set_version_info` /
  `set_manifest` 的签名与 `winresource 0.1.31` 一致)。
- 取 winresource 真实生成的 `resource.rc` 手工喂 `llvm-rc`:退出码 0,`.res` 资源目录读出
  `VERSION 1 / ICON 1..6 / GROUP_ICON 1 / RT_MANIFEST 1(1120 字节)`—— 清单落在加载器唯一认的
  「type 24 / id 1」位置。
- 把该 `.res` 当 `.rsrc` 负荷塞进合成 PE:门禁判 OK;对**真实 v0.20.1 的 navop.exe** 判 FAIL
  (退出码 1,报错含 `TaskDialogIndirect` 与 `0xC0000139`)。
- 未在本机验证:MSVC rc.exe / link.exe 真实产物能否正常启动、DPI 表现如何(需 Windows)。
  CI 的 `windows-2022` 腿会编译这段代码。
2026-10-13 19:54:41 +08:00
..
2026-02-15 11:51:18 +08:00