Deep Analysis 与 ProbeHost 实现说明

May 29, 2026 · View on GitHub

1. Deep Analysis 的目的

Deep Analysis 用于尝试解析 Shell Extension 实际插入到右键菜单中的菜单文字、图标、canonical verb 和帮助文本。它不是普通菜单开关功能,也不是审核流程的必要条件。

普通 snapshot 只能看到注册表中的 handler、CLSID、命令和路径;第三方 Shell Extension 真正添加哪些菜单项,需要在进程中创建 COM handler 并调用 IContextMenu.QueryContextMenu。这一步风险高,所以放到 ProbeHost。

2. 为什么需要 ProbeHost

第三方 Shell Extension 通常是 in-process COM DLL。直接在前端或后端加载会带来这些风险:

风险说明
崩溃第三方 DLL 的 access violation 会拖垮宿主进程。
卡死handler 可能阻塞 Explorer 或宿主线程。
异常行为handler 可能依赖 Explorer 环境、选中文件、线程模型或外部组件。
架构限制x86 handler 不能加载到 x64 进程,arm64 也有类似限制。

ContextMenuMgr.ProbeHost 是短生命周期 native C++ Win32 隔离进程。它可以崩溃或超时,而不影响前端、后端服务和普通菜单管理。

3. ProbeHost 的边界

ProbeHost 必须保持边界清晰:

边界说明
不写注册表ProbeHost 只读取必要的 COM 信息并执行探测。
不执行菜单命令它只枚举菜单文本,不调用菜单命令。
不常驻只在用户点击 Deep Analysis 时由前端启动。
不参与普通扫描普通 snapshot 不依赖 ProbeHost。
可失败超时、崩溃、无结果、COM 初始化失败都是可接受失败。

不要把 ProbeHost 当提权链路。它由前端启动,解决的是第三方 COM 隔离和架构匹配,不解决管理员权限。

4. 架构选择

当前前端 ContextMenuDeepAnalysisService 会根据 handler DLL 的 PE machine type 选择 ProbeHost:

handler 架构选择目录
x86ProbeHost\x86\ContextMenuMgr.ProbeHost.exe
x64ProbeHost\x64\ContextMenuMgr.ProbeHost.exe
arm64ProbeHost\arm64\ContextMenuMgr.ProbeHost.exe
未知回退到当前前端进程架构,并允许 root fallback。

ARM64 Windows 上也可能需要 x64 或 x86 ProbeHost,因为第三方 handler 可能不是 arm64。ProbeHost 架构必须匹配目标 Shell Extension DLL,否则 CoCreateInstance 或 DLL 加载会失败,严重时会导致 native crash。

Debug 和 Release 构建产物都要验证目录标签与实际 PE machine type 一致。Scripts/Verify-ProbeHostArchitecture.ps1 会检查 x86x64arm64 目录中的 ContextMenuMgr.ProbeHost.exe 是否对应预期 machine 值。

5. SpecificHandler 与 WholeContextMenu

模式说明注意事项
SpecificHandler只 CoCreate 当前 HandlerClsid,初始化该 handler,再调用 IContextMenu.QueryContextMenu结果更接近当前条目,但更容易因 handler 不支持 IShellExtInit 或初始化上下文而失败。
WholeContextMenu创建示例目标的完整 shell context menu,再枚举整个菜单。结果可能包含其它软件、系统菜单项和 Explorer 自身菜单,不能当作当前 handler 的精确结果。

不要自动把 SpecificHandler 失败后 WholeContextMenu 成功解释成“当前 handler 成功”。这两个模式回答的问题不同。

6. 请求与结果流程

Frontend
-> ContextMenuDeepAnalysisService.AnalyzeAsync
-> 解析 HandlerFilePath / CLSID
-> 选择 ProbeHost 架构
-> 写 request.json 到临时目录
-> 启动 ContextMenuMgr.ProbeHost.exe
-> ProbeHost 解析 COM / IContextMenu
-> 写 result.json 或输出 JSON
-> Frontend 捕获 stdout / stderr
-> 读取 ContextMenuDeepAnalysisResult
-> ContextMenuDeepAnalysisWindow 展示

ContextMenuDeepAnalysisRequestContextMenuDeepAnalysisResult 定义在 ContextMenuMgr.Contracts/ContextMenuDeepAnalysisContracts.cs。前端会设置 WorkingDirectory 为 ProbeHost 所在目录,并捕获 stdout、stderr、exit code 和 result file。

结果中的菜单项可选包含 iconPngBase64。该字段只表示 ProbeHost 已成功把菜单项的运行时图标编码为 PNG base64;缺失或为空表示没有可用图标或图标提取失败。旧结果 JSON 不包含该字段时仍应正常反序列化。

7. 菜单项图标

ProbeHost 在枚举 HMENU 时通过 MENUITEMINFO.hbmpItem best-effort 读取标准菜单位图图标,并在能安全转换时把 PNG base64 写入 iconPngBase64。图标提取失败只影响单个菜单项图标,不应导致 QueryContextMenu 结果失败,也不应让 ProbeHost 进程失败。

当前只支持 Shell Extension 通过 MENUITEMINFO.hbmpItem 暴露的标准 HBITMAP。以下情况会被有意跳过:

  • HBMMENU_* 预定义值和 HBMMENU_CALLBACK
  • owner-draw 菜单项;
  • 回调图标或扩展自绘图标;
  • 无法安全转换或编码后过大的位图。

缺少图标是正常现象,不能单独视为 Deep Analysis 或普通菜单管理 Bug。

8. 失败分类

很多失败是正常限制,不应直接提示用户报告 Bug。

错误码含义
MissingX86ProbeHost / MissingX64ProbeHost / MissingArm64ProbeHost对应架构的 ProbeHost 不存在。
ProbeHostExecutableArchitectureMismatch目录标签和 ProbeHost exe 实际 PE 架构不一致。
ArchitectureMismatchProbeHost 进程架构与 handler DLL 架构不兼容。
CoCreateHandlerFailed / CoCreateHandlerNoIUnknown创建 COM handler 失败。
ShellExtInitNotSupportedhandler 不支持 IShellExtInit
IContextMenuNotSupportedhandler 不支持 IContextMenu
QueryContextMenuFailed调用 IContextMenu.QueryContextMenu 失败。
SpecificHandlerReturnedNoItemshandler 成功返回但没有可显示项。
ProbeHostNativeCrash / ProbeHostNativeAccessViolation / ProbeHostStackBufferOverrunProbeHost 被第三方 native 代码拖崩。
Timeout超过默认或传入超时时间,前端会尝试结束进程。
InvalidProbeHostJson / InvalidProbeHostOutputProbeHost 未返回合法 JSON。

9. 构建与部署注意事项

ProbeHost 必须随前端部署多架构目录。当前 ContextMenuMgr.Frontend.csproj 会用 MSBuild 构建 native C++ ProbeHost。Debug 本地开发默认构建并复制 x86 / x64,以便未安装 ARM64 C++ 工具链的开发机可以直接运行前端;Release / Beta 和发布构建仍默认构建并复制 x86 / x64 / arm64。需要在 Debug 中强制构建三架构时可传入 -p:NativeProbeHostPlatforms=Win32,x64,ARM64

构建后复制:

  • ContextMenuMgr.ProbeHost.exe

ProbeHost\x86ProbeHost\x64ProbeHost\arm64。ProbeHost 不再携带 .dll.deps.json.runtimeconfig.jsonContextMenuMgr.Contracts.dll

ProbeHost 的 JSON 解析使用 vendored nlohmann/json 单头文件依赖:

  • 源文件位置:ContextMenuMgr.ProbeHost\third_party\nlohmann\json.hpp
  • 不使用 vcpkg、Conan、NuGet 或 Git submodule;
  • json.hpp 不复制到 runtime output;
  • MIT license 复制到 ThirdPartyNotices\nlohmann-json-LICENSE.MIT
  • header-only 代码会编译进 ContextMenuMgr.ProbeHost.exe

ProbeHost 的菜单图标 PNG 编码使用 Windows 内置 WIC,并链接 windowscodecs.lib;不引入第三方图片库或新的运行时文件。

Release 发布由 Scripts/Build.Common.psm1Get-ProbeHostArchitectureMap 决定携带哪些架构。win-x86 只带 x86,win-x64 带 x64 和 x86,win-arm64 带 arm64、x64 和 x86。脚本使用 MSBuild 构建 ContextMenuMgr.ProbeHost.vcxproj,不再对 ProbeHost 运行 dotnet restoredotnet publish

10. 常见坑

正确处理
在前端或后端直接 CoCreateInstance 第三方 handler只能通过 ProbeHost 隔离。
把 ProbeHost 当提权进程它不是提权链路。
SpecificHandler 失败后把 WholeContextMenu 成功当作当前 handler 成功两者结果语义不同。
把 menu accelerator & 当乱码& 是 Windows 菜单助记符语义。
把缺失图标当作错误图标提取是 best-effort;自绘、回调或无法转换的图标可能为空。
把 ProbeHost crash 当作普通菜单开关失败Deep Analysis 失败不应影响开关、删除、审核。
忘记设置 WorkingDirectoryframework-dependent 依赖解析可能失败。
把 x86 binary 放进 arm64 目录架构验证会失败,运行时也会误判。