概述:二十年后的 Shell 扩展

面向 Windows 10/11 重写的 Shell 扩展开发指南。

作者 David更新于

二十多年前,Michael Dunn 在 CodeProject 写了一套 The Complete Idiot's Guide to Writing Shell Extensions,我把它翻译成中文放在个人主页上,是很多人入门 Windows Shell 编程的第一站。但那套教程停在 2004 年——它讲的 IColumnProvider、IExtractImage、IPersistFile 初始化、乃至整套注册方式,在 Windows 10/11 上要么被取代、要么被移除、要么已经不是推荐做法。

这份指南不是翻译,而是重写:保留原系列由浅入深的例子脉络,但每一个都按 Win10/Win11 的现状与最新官方文档重新构思。示例代码单独放在开源仓库 davidapp/shell-extension-demos 里维护(每个示例一个目录、自带 CMake 工程,clone 即可构建),正文只保留讲清原理所需的关键片段。

重点放在 Windows 10 与 Windows 11。凡涉及“老写法仍能用、但有更好的新做法”的地方,都会明确标注,并给出官方文档链接。

什么是 Shell 扩展

Windows 资源管理器(Explorer)本质上是一个可扩展的外壳。Shell 扩展就是你写的一个进程内 COM 组件(通常是一个 DLL),资源管理器在需要时把它加载进来,让你在特定环节“插一脚”:

  • 用户右键一个文件 → 让你往快捷菜单里加命令;
  • 资源管理器要画某个文件的缩略图 / 图标 → 让你提供自定义图像;
  • 用户打开属性对话框、悬停看信息提示、在详细信息视图里排序分组 → 让你提供数据;
  • 用户拖放、预览文件 → 让你接管。

关键区别:注册表也能做一些定制(比如给某类文件配一个固定图标),但注册表只能“一刀切”——同一类型的所有文件长一个样。Shell 扩展则能逐文件决定行为(这个文本文件显示这个图标、那个显示那个)。这正是官方文档里区分“注册表定制”与“扩展处理器(handler)”的分界线。

二十年后:到底变了什么

如果你读过老版指南,先看这张对照表——它决定了这份新指南每一章要怎么写:

老指南里的例子当年的接口Windows 10/11 现状
文本文件右键菜单IContextMenu仍可用;但 Win11 新版右键菜单需要 IExplorerCommand + MSIX 打包,未打包的处理器只落到「显示更多选项」旧菜单里
多文件右键 + regsvr32 注册IContextMenu逻辑不变;注册未打包走注册表、打包走包清单
弹出信息提示(用 MFC)IQueryInfo仍可用;更推荐走属性系统(prop: 字符串或 IPropertyStore)
拖放建硬链接IDropTarget仍可用
文件属性页IShellPropSheetExt仍可用
「复制到 / 发送到」右键IContextMenu(右键拖放)仍可用;配套的 DDE 已弃用,改用 IDropTarget 激活
文件夹背景菜单 + BMP 缩略图IExtractImage缩略图改用 IThumbnailProvider(Vista 起推荐)
详细信息列(列处理器,读 MP3 标签)IColumnProviderVista 起已移除;改用属性处理器 IPropertyStore + 属性 schema
按文件大小显示不同图标IExtractIcon仍可用

除此之外,还有两处“地基级”的变化贯穿所有处理器:

  • 初始化接口:老代码用 IPersistFile / IShellExtInit 拿到文件名。Vista 起这些被 IInitializeWithItem、IInitializeWithStream、IInitializeWithFile 取代(图标处理器现在也不再强制 IPersistFile)。
  • 属性系统统一化:详细信息列、信息提示、属性页里的字段、搜索与分组,如今都可以由一个属性处理器(IPropertyStore)统一供数据,不再各写各的。老指南里“列处理器”这一章,在新版里会整体并入“属性处理器”。

IColumnProvider(列处理器)在 Windows Vista 就被移除了,别再照老教程写。要往详细信息视图加列,正确做法是实现属性处理器并注册属性 schema——本指南有专门一章。

你需要的工具

  • Windows 11(或 Windows 10)+ 一台能随便重启资源管理器、装卸扩展的开发机(强烈建议用虚拟机,扩展写崩了会拖垮 Explorer)。
  • Visual Studio 2022,含「使用 C++ 的桌面开发」与「通用 Windows 平台开发 / MSIX 打包工具」工作负载。
  • C++17、Windows SDK(较新版本)。
  • WIL(Windows Implementation Library):微软官方的现代 C++ 头文件库,wil::com_ptr、RAII 句柄、错误处理宏能让 COM 代码干净很多。老教程里的 ATL 仍可用,但新代码更推荐 WIL(可与 ATL 并存)。
  • 调试利器:Process Monitor、DebugView,以及资源管理器的独立进程选项(方便挂调试器、崩了不连累桌面)。

微软把大量现成、可编译的扩展范例放在 Windows-classic-samples 仓库(搜 ThumbnailProvider、PreviewHandler、ExplorerCommandVerb、PropertyHandler 等目录)。本指南的示例仓库会以它们为骨架,去掉过时写法、补齐 Win11 打包。

先打好地基:COM 与 DLL

绝大多数 Shell 扩展是进程内(in-process)COM 对象,打包成 DLL。要成为一个合格的扩展,它至少要:

  • 有一个唯一的 GUID(CLSID);
  • 实现 IUnknown 与一个类工厂(class factory);
  • 从 DLL 导出三个标准函数:DllGetClassObject(暴露类工厂)、DllCanUnloadNow(告诉系统没人用了可以卸载)、DllMain;
  • 实现对应处理器的接口(IContextMenu、IThumbnailProvider……)以及现代初始化接口(IInitializeWithItem 等)。

线程模型统一用 Apartment。这些“样板”在每个扩展里几乎一样,示例仓库会把它抽成可复用的基类,正文则聚焦每种处理器独有的接口。

注册:注册表 与 MSIX 两条路

同一个处理器,在 Win10/11 上有两种“让系统认识它”的方式,本指南两条都会讲:

  1. 注册表(未打包):经典做法。在 HKEY_CLASSES_ROOT 下写 CLSID\{GUID}\InProcServer32 指向 DLL,再到文件类型的 ShellEx\<处理器子键> 下挂上 GUID。改动后要调用 SHChangeNotify(SHCNE_ASSOCCHANGED, …) 通知系统。适合快速开发、内部工具。
  2. MSIX / 稀疏包(packaged):现代做法,也是进入 Windows 11 新版右键菜单的唯一途径。用包清单(appxmanifest)里的 com:Extension + desktop4/desktop5:Extension 声明扩展,系统按包来加载、更新、卸载,干净且可上架。缩略图、预览、右键命令等都能这样注册。

进程内扩展是被加载进资源管理器(或其它宿主进程)里跑的——一旦你的代码崩了或死锁,轻则该功能失灵,重则整个桌面卡死。所以每个扩展都要:严格判空与错误处理、绝不在 UI 线程做耗时/阻塞操作、务必在虚拟机里测。可能的话优先用进程外代理宿主(如预览处理器天然跑在 prevhost.exe 里)。

本指南结构

按“一次一个例子”推进,每一章聚焦一种处理器,给出可运行的最小示例 + Win10/11 注意点 + 官方文档链接:

  1. 右键菜单扩展(IContextMenu) —— 第一个 in-proc DLL 扩展,把 COM/DLL 地基走通
  2. 现代命令模型 IExplorerCommand —— 更简单的命令写法、级联子菜单、动态启用/隐藏
  3. Windows 11 新版右键菜单 —— 用 MSIX / 稀疏包把命令送进主菜单
  4. 缩略图处理器 IThumbnailProvider —— 取代 IExtractImage
  5. 图标处理器 IExtractIcon 与图标叠加 IShellIconOverlayIdentifier
  6. 属性处理器 IPropertyStore 与属性 schema —— 详细信息列 / 分组 / 信息提示的统一数据源(取代列处理器)
  7. 信息提示 IQueryInfo
  8. 属性表页 IShellPropSheetExt
  9. 预览处理器 IPreviewHandler
  10. 拖放处理器 IDropTarget
  11. 打包、部署、调试与旧代码迁移

章节顺序可能随写作微调;每章配套的完整、可编译示例见开源仓库 davidapp/shell-extension-demos。第 ① 章的 01-context-menu 已可直接构建运行。

参考

本页目录