Mini 外壳扩展框架 - 第 I 部分






4.50/5 (6投票s)
2004 年 8 月 23 日
6分钟阅读

78844

960
讨论一个用于创建 Windows Shell 扩展的小型 C++ 框架。
引言
本文讨论了一个可用于创建 Windows Shell 扩展的 Mini Shell 扩展框架。Windows Shell 扩展允许在标准 Windows Shell(Explorer)中为特定类型的文件添加扩展功能。有关多种 Shell 扩展的完整详细讨论,请参阅 CodeProject 网站上 Michael Dunn 的优秀文章。
该框架由一组 C++ 模板组成,可与 ATL(7.0 和 7.1)结合使用来实现所需的 COM 对象。该框架包含模板头文件、一个示例和一个小型单元测试应用程序。要使用该框架并在 Visual C++ .NET 2002 中构建示例,需要 Platform SDK February 2003 的头文件(可从 Microsoft 网站下载)。Visual C++ .NET 2003 已包含更新的 SDK 文件。
设计理念
从概念上讲,Shell 扩展的开发并不难。使用 ATL,可以轻松创建支持所需接口的 COM 对象。但是,创建错误处理代码、注册脚本、本地化支持和测试代码会使实现变得复杂。
该框架的目标是提供一组简单的类,帮助将这类“管道”代码移出 Shell 扩展,转移到“实现”辅助类中。这样,Shell 扩展的作者就可以专注于扩展的功能。
所有框架类都包含在 MSF 命名空间中,并拥有自己的包含文件。这使得在使用框架的某个类时,无需引入其他任何类。
框架类假定任何意外错误都会引发异常。可能抛出的异常类型是 _com_error
类(HRESULT
错误)和 std::bad_alloc
类(内存分配错误)。
类概述
Shell 扩展支持类:IColumnProviderImpl
, IContextMenuImpl
, IInfoTipImpl
, IShellFolderImpl
, IDataObjectPtr
, IShellFolderViewCBImpl
。
测试支持类:CCoInitialize
, CTestRunner
, IShellPropSheetExtPtr
, CFileList
, CPropSheetHost
。
VVV 示例
为了展示如何使用这些类并提供创建 Shell 扩展的示例,代码包含了一个可以打开 .vvv 文件的简单示例。VVV 文件只是重命名的 .ini 文件,用作容器。大多数 Shell 扩展都是容器视图(例如,Windows 的 .zip 和 .cab Shell 扩展)。
自动化测试:测试示例
手动验证 Shell 扩展是否正常工作是可能的,但自动化测试更为有效。测试示例提供了一个小型测试应用程序,可以执行 Shell 扩展的自动化测试。已使用框架提供的测试类提供了一些默认测试。测试示例也是使用商业内存错误检测工具跟踪内存错误的理想应用程序(通常使用 explorer.exe 并不实用)。
调试和跟踪
调试 Shell 扩展的最简单方法是输出调试字符串。ATL 支持 TRACE
宏。这些日志语句可以通过附加调试器或使用来自 sysinternals 的免费 DebugView 工具捕获。
如果使用 _ATL_DEBUG_QI
编译,ATL 还会显示请求了哪些接口。由于 ATL 中的一个 bug,它仅在接口在注册表中注册时打印接口名称。该框架包含一个名为“Undocumented Shell Interfaces.reg”的文件,可用于将缺失的信息输入注册表。
创建 InfoTip
InfoTip 是一种工具提示,当用户将鼠标指针悬停在 Explorer 窗口中的文件上时,会显示有关该文件的信息。因此,InfoTip 对象的基本任务是返回“信息”。
virtual CString CInfoTip::GetInfoTip(const TCHAR* szFilename) { return _T(“info”); }
GetInfoTip
函数必须由扩展实现,所有其他必需的函数(大部分都是存根)都由 IInfoTipImpl
处理。
COM 对象在执行其任务之前,必须进行注册。
HRESULT WINAPI CinfoTip::UpdateRegistry(BOOL bRegister) throw() { return IInfoTipImpl<CInfoTip>::UpdateRegistry( IDR_INFOTIP, IDR_EXTENSION, bRegister, L"Sample InfoTip", L“VVVFile”, L“.vvv”); }
框架提供了注册资源脚本 IDR_INFOTIP
和 IDR_EXTENSION
,但必须将其包含在 DLL 的资源文件中。还有三个额外的参数:描述、根扩展名和文件扩展名。所有注册的文件扩展名都指向同一个根扩展名。在示例中,仅注册了一个文件扩展名,但框架允许将更多文件扩展名注册到同一个根。完整的类定义是
class ATL_NO_VTABLE CInfoTip : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CInfoTip &__uuidof(CInfoTip)>, public MSF::IInfoTipImpl<CInfoTip> { public: BEGIN_COM_MAP(CInfoTip) COM_INTERFACE_ENTRY(IPersistFile) COM_INTERFACE_ENTRY(IQueryInfo) END_COM_MAP() static HRESULT WINAPI UpdateRegistry(BOOL bRegister) throw(); virtual CString GetInfoTip(const TCHAR* szFilename); };
创建 ColumnProvider
ColumnProvider 是一个 COM 对象,Shell 会查询它以获取要在 Explorer 窗口的详细视图模式中显示的其他信息。框架模型简单但有效。它假定计算特定文件的所有列的成本很低。由于 Shell 不缓存文件信息,IColumnProviderImpl
类会确保缓存每一列。缓存对于确保在提供的列上进行排序具有可接受的性能至关重要。使用 CColumnProviderImpl
类的三个基本步骤是
COM 对象注册
static HRESULT WINAPI UpdateRegistry(BOOL bRegister) throw() { return IColumnProviderImpl<CColumnProvider>:: UpdateRegistry(IDR_COLUMNPROVIDER, bRegister, L"Sample ColumnProvider"); }
支持的文件扩展名和列名的配置
CColumnProvider() { RegisterColumn(IDS_SHELLEXT_NAME, 9); RegisterColumn(IDS_SHELLEXT_FILECOUNT, 14, LVCFMT_RIGHT, SHCOLSTATE_TYPE_INT); RegisterExtension(L".vvv"); }
在上面的代码示例中,我们配置了两列。第一列是“name”,初始宽度为九个字符。下一列是“filecount”,右对齐,并由 Shell 作为 int
来排序。ColumnProvider 只对 .vvv 文件感兴趣。
virtual void CacheFile(const CString& strFilename, vector<CString>& strColumnInfos) { CVVVFile vvvfile(strFilename); strColumnInfos.push_back(vvvfile.GetName()); strColumnInfos.push_back(vvvfile.GetFileCount()); }
当 Shell 需要列信息时,CColumnProviderImpl
会将请求转发到 CacheFile
函数。列信息应按在构造函数中注册的列的相同顺序作为字符串添加到输出数组中。如果在检索列信息期间发生任何错误,允许该函数抛出 com_error
异常。
创建 Shell 属性表扩展和属性表页
Shell 使用的标准属性表页可以与我们的自定义页面进行扩展。要做到这一点,必须使用 ShellPropSheetExt
COM 对象。使用框架提供的 CShellPropSheetExtImpl
类,需要实现三个函数。
COM 对象注册
static HRESULT WINAPI UpdateRegistry(BOOL bRegister) throw() { return CshellPropSheetExtImpl<CShellPropSheetExt>:: UpdateRegistryFromResource(IDR_PROPSHEETEXT, IDR_EXTENSION, bRegister, L"Sample PropertySheet", L”VVVFile”, L”.vvv”); }
IDR_PROPSHEETEXT
指的是框架提供的注册资源脚本(.rgs),该脚本使用参数来注册 COM 对象。在这种情况下,参数是描述、根扩展名和文件扩展名。
接下来的几行显示了构造函数。
CShellPropSheetExt()
{
RegisterExtension(_T(".vvv"));
}
在我们的示例中,我们仅为 .vvv 文件注册了 propsheetext
。我们可以添加更多希望为其提供属性页的文件扩展名。
需要实现的最后一个函数是 AddPages
函数。
virtual void AddPages(const CAddPage& addpage, const std::vector<CString>& filenames, bool bContainsUnknownExtensions) { if (bContainsUnknownExtensions || filenames.size() != 1) return; addpage(CPropertyPageVVV::CreateInstance(filenames[0])); }
在我们的示例中,如果用户选择了多个文件或选择了没有 .vvv 扩展名的文件,我们不会添加任何页面。如果不是这种情况,我们会调用仿函数 addpage
,并将一个由 CreateInstance
函数创建的 HPROPSHEETPAGE
句柄传递给它。
框架提供了 CShellExtPropertyPageImpl
类,该类派生自 ATL 类 CSnapInPropertyPageImpl
,用于创建属性页。新增的功能是 MSF 类锁定 COM 模块,以防止 DLL 被卸载。属性表的生存期不与 COM ShellPropSheetExt
对象绑定,因为它不是 COM 对象。
后续说明
如果您构建并执行示例,您会发现 VVV 文件也可以在 Explorer 中打开。此功能通过自定义 shellfolder
对象提供。我计划在本文的第二部分详细介绍此 shellfolderimpl
,因为其功能复杂,会使本文过长。同样的情况也适用于额外的上下文菜单项。尽管如此,该框架目前已经包含了一个功能齐全的 IShellFolderImpl
和 IContextMenuImpl
类(附有示例代码)。
历史
- 版本 0.85:为 CodeProject 文章改进的发布。
- 版本 0.80:初始发布。