概述:二十年后的 Shell 扩展
面向 Windows 10/11 重写的 Shell 扩展开发指南。
二十多年前,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 标签) | IColumnProvider | Vista 起已移除;改用属性处理器 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 上有两种“让系统认识它”的方式,本指南两条都会讲:
- 注册表(未打包):经典做法。在
HKEY_CLASSES_ROOT下写CLSID\{GUID}\InProcServer32指向 DLL,再到文件类型的ShellEx\<处理器子键>下挂上 GUID。改动后要调用SHChangeNotify(SHCNE_ASSOCCHANGED, …)通知系统。适合快速开发、内部工具。 - MSIX / 稀疏包(packaged):现代做法,也是进入 Windows 11 新版右键菜单的唯一途径。用包清单(
appxmanifest)里的com:Extension+desktop4/desktop5:Extension声明扩展,系统按包来加载、更新、卸载,干净且可上架。缩略图、预览、右键命令等都能这样注册。
进程内扩展是被加载进资源管理器(或其它宿主进程)里跑的——一旦你的代码崩了或死锁,轻则该功能失灵,重则整个桌面卡死。所以每个扩展都要:严格判空与错误处理、绝不在 UI 线程做耗时/阻塞操作、务必在虚拟机里测。可能的话优先用进程外代理宿主(如预览处理器天然跑在 prevhost.exe 里)。
本指南结构
按“一次一个例子”推进,每一章聚焦一种处理器,给出可运行的最小示例 + Win10/11 注意点 + 官方文档链接:
- 右键菜单扩展(
IContextMenu) —— 第一个 in-proc DLL 扩展,把 COM/DLL 地基走通 - 现代命令模型
IExplorerCommand—— 更简单的命令写法、级联子菜单、动态启用/隐藏 - Windows 11 新版右键菜单 —— 用 MSIX / 稀疏包把命令送进主菜单
- 缩略图处理器
IThumbnailProvider—— 取代IExtractImage - 图标处理器
IExtractIcon与图标叠加IShellIconOverlayIdentifier - 属性处理器
IPropertyStore与属性 schema —— 详细信息列 / 分组 / 信息提示的统一数据源(取代列处理器) - 信息提示
IQueryInfo - 属性表页
IShellPropSheetExt - 预览处理器
IPreviewHandler - 拖放处理器
IDropTarget - 打包、部署、调试与旧代码迁移
章节顺序可能随写作微调;每章配套的完整、可编译示例见开源仓库 davidapp/shell-extension-demos。第 ① 章的
01-context-menu已可直接构建运行。