如何使用 .NET 语言编写 Windows Shell 扩展
C# 代码示例演示了如何使用 .NET Framework 4 创建 Shell 上下文菜单处理程序
关于我们
Microsoft All-In-One Code Framework (http://1code.codeplex.com) 是一个由开发人员需求驱动的免费、集中式代码示例库。我们的目标是为所有 Microsoft 开发技术提供典型的代码示例,并减少开发人员在解决典型编程任务上的工作量。
我们的团队倾听 MSDN 论坛、社交媒体和各种开发社区中开发人员的痛点。我们根据开发人员经常遇到的编程任务编写代码示例,并允许开发人员在短的代码示例发布周期内下载它们。此外,我们的团队还提供免费代码示例请求服务。这项服务是我们的开发人员社区直接从 Microsoft 获取特定编程任务代码示例的一种主动方式。
引言
在 MSDN 论坛中,许多开发人员询问如何使用 .NET 语言(例如 C#、VB.NET)编写 Windows Shell 扩展。
- http://social.msdn.microsoft.com/Forums/en-US/clr/thread/7ceb44d5-dce8-4197-ac55-f0f4fb59eeb4/
- http://social.msdn.microsoft.com/Forums/en-US/clr/thread/7ce0c480-59e3-4732-a608-1974a908e44a/
- http://social.msdn.microsoft.com/forums/en-US/netfxbcl/thread/1428326d-7950-42b4-ad94-8e962124043e
- http://social.msdn.microsoft.com/Forums/en-US/clr/thread/63d04f72-5c71-40a9-aea3-519c9e9591a6
在 .NET Framework 4 之前,使用托管代码开发进程内 Shell 扩展并未得到官方支持,因为 CLR 限制了每个进程只能加载一个 .NET 运行时。CLR 项目经理之一 Jesse Kaplan 在此 MSDN 论坛帖子中对此进行了说明。
在 .NET 4 中,由于可以与任何其他运行时在进程内拥有多个运行时,Microsoft 现在可以为编写托管 Shell 扩展提供通用支持——即使是那些在进程内与计算机上的任意应用程序一起运行的扩展。本文详细介绍了进程内并行功能。但是,请注意,您仍然无法使用 .NET Framework 4 之前的任何版本编写 Shell 扩展,因为这些版本的运行时无法在进程内加载,并且在许多情况下会导致失败。
本文档解释了理论。我到底该如何编写托管 Shell 扩展?
如果您在互联网上搜索,会发现几乎没有 .NET 4 Shell 扩展示例。为数不多的 .NET 2 Shell 扩展示例(由于上述原因不受支持)或多或少存在一些缺陷,例如无法在 x64 环境中加载。为了满足客户需求,我们,All-In-One Code Framework 项目组,将填补这一空白。项目组已计划一系列 .NET 4 托管 Shell 扩展代码示例,涵盖上下文菜单处理程序、属性表处理程序、图标处理程序、数据处理程序、删除处理程序、拖放处理程序、缩略图处理程序、图标处理程序、图标覆盖处理程序等。本文介绍第一个示例:上下文菜单处理程序。
CSShellExtContextMenuHandler
:Shell 上下文菜单处理程序 (C#)VBShellExtContextMenuHandler
:Shell 上下文菜单处理程序 (VB.NET)CppShellExtContextMenuHandler
:Shell 上下文菜单处理程序 (C++)
演示
以下是上下文菜单处理程序代码示例的快速演示。在 Visual Studio 2010 中成功构建示例项目 CSShellExtContextMenuHandler
后,您将获得一个 DLL:CSShellExtContextMenuHandler.dll。在 Microsoft Visual Studio 2010 \ Visual Studio Tools 菜单中以管理员身份运行“Visual Studio 命令提示符 (2010)”(如果在 x64 操作系统上,则运行“Visual Studio x64 Win64 命令提示符 (2010)”)。导航到包含生成结果 CSShellExtContextMenuHandler.dll 的文件夹,然后输入命令
Regasm.exe CSShellExtContextMenuHandler.dll /codebase
来注册上下文菜单处理程序。
在 Windows 资源管理器中找到一个 .cs 文件(例如,示例文件夹中的 FileContextMenuExt.cs),然后右键单击它。您将在上下文菜单中看到“Display File Name (C#)”菜单项,以及其下方的菜单分隔符。单击菜单项会弹出一个消息框,显示 .cs 文件的完整路径。

实现细节
A. 创建和配置项目
在 Visual Studio 2010 中,创建一个名为“CSShellExtContextMenuHandler
”的 Visual C# / Windows / 类库项目。打开项目属性,然后在“签名”页面中,使用强名称密钥文件为程序集签名。
B. 实现基本的组件对象模型 (COM) DLL
Shell 扩展处理程序都是进程内 COM 对象,实现为 DLL。创建一个基本的 .NET COM 组件非常直接。您只需要定义一个带有 ComVisible(true)
的“public
”类,使用 Guid
属性指定其 CLSID
,并显式实现某些 COM 接口。例如
[ClassInterface(ClassInterfaceType.None)]
[Guid("B1F1405D-94A1-4692-B72F-FC8CAF8B8700"), ComVisible(true)]
public class SimpleObject : ISimpleObject
{
... // Implements the interface
}
您甚至不需要自己实现 IUnknown
和类工厂,因为 .NET Framework 会为您处理这些。
C. 实现上下文菜单处理程序并将其注册到特定文件类
实现上下文菜单处理程序
FileContextMenuExt.cs 文件定义了一个上下文菜单处理程序。上下文菜单处理程序必须实现 IShellExtInit
和 IContextMenu
接口。这些接口使用 COMImport
属性在 ShellExtLib.cs 中导入。
[ComImport(),InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214e8-0000-0000-c000-000000000046")]
internal interface IShellExtInit
{
void Initialize(
IntPtr pidlFolder,
IntPtr pDataObj,
IntPtr /*HKEY*/ hKeyProgID);
}
[ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214e4-0000-0000-c000-000000000046")]
internal interface IContextMenu
{
[PreserveSig]
int QueryContextMenu(
IntPtr /*HMENU*/ hMenu,
uint iMenu,
uint idCmdFirst,
uint idCmdLast,
uint uFlags);
void InvokeCommand(IntPtr pici);
void GetCommandString(
UIntPtr idCmd,
uint uFlags,
IntPtr pReserved,
StringBuilder pszName,
uint cchMax);
}
[ClassInterface(ClassInterfaceType.None)]
[Guid("B1F1405D-94A1-4692-B72F-FC8CAF8B8700"), ComVisible(true)]
public class FileContextMenuExt : IShellExtInit, IContextMenu
{
public void Initialize(IntPtr pidlFolder, IntPtr pDataObj, IntPtr hKeyProgID)
{
...
}
public int QueryContextMenu(
IntPtr hMenu,
uint iMenu,
uint idCmdFirst,
uint idCmdLast,
uint uFlags)
{
...
}
public void InvokeCommand(IntPtr pici)
{
...
}
public void GetCommandString(
UIntPtr idCmd,
uint uFlags,
IntPtr pReserved,
StringBuilder pszName,
uint cchMax)
{
...
}
}
PreserveSig
属性表示在 COM 互操作调用期间发生的 HRESULT
或 retval
签名转换将被抑制。当您不应用 PreserveSigAttribute
时(例如,IContextMenu
的 GetCommandString
方法),方法失败的 HRESULT
需要作为 .NET 异常抛出。例如,Marshal.ThrowExceptionForHR(WinError.E_FAIL);
当您将 PreserveSigAttribute
应用于托管方法签名时,该属性化方法的托管和非托管签名是相同的(例如,IContextMenu
的 QueryContextMenu
方法)。如果成员返回多个成功的 HRESULT
值,并且您希望检测这些不同的值,则有必要保留原始方法签名。
当用户显示已注册上下文菜单处理程序的类的对象的上下文菜单时,上下文菜单扩展会被实例化。
1. 实现 IShellExtInit
上下文菜单扩展 COM 对象被实例化后,会调用 IShellExtInit.Initialize
方法。IShellExtInit.Initialize
向上下文菜单扩展提供一个 IDataObject
对象,该对象以 CF_HDROP
格式包含一个或多个文件名。您可以通过 IDataObject
对象枚举选定的文件和文件夹。如果从 IShellExtInit.Initialize
返回失败的 HRESULT
(抛出异常),则不会使用上下文菜单扩展。
在代码示例中,FileContextMenuExt.Initialize
方法枚举选定的文件和文件夹。如果只选择了一个文件,该方法会将文件名存储起来供以后使用。如果选择了多个文件或未选择任何文件,该方法将抛出带有 E_FAIL HRESULT
的异常,以不使用上下文菜单扩展。
2. 实现 IContextMenu
在 IShellExtInit.Initialize
成功返回后,会调用 IContextMenu.QueryContextMenu
方法来获取上下文菜单扩展将添加的菜单项。QueryContextMenu
的实现相当直接。上下文菜单扩展使用 InsertMenuItem
或类似函数添加其菜单项。菜单命令标识符必须大于或等于 idCmdFirst
并且小于 idCmdLast
。QueryContextMenu
必须返回添加到菜单的最大数字标识符加一。分配菜单命令标识符的最佳方法是从零开始按顺序递增。如果上下文菜单扩展不需要向菜单添加任何项,它应该简单地从 QueryContextMenu
返回。
在此代码示例中,我们插入菜单项“Display File Name (C#)”并在其下方添加一个菜单分隔符。
调用 IContextMenu.GetCommandString
来检索菜单项的文本数据,例如为菜单项显示的帮助文本。如果用户突出显示了由上下文菜单处理程序添加的某个项,则会调用处理程序的 IContextMenu.GetCommandString
方法来请求将在 Windows 资源管理器状态栏显示的帮助文本字符串。此方法还可以用于请求分配给命令的动词字符串。可以请求 ANSI 或 Unicode 动词字符串。此示例仅实现了对 uFlags
Unicode 值 else 的支持,因为自 Windows 2000 以来,Windows 资源管理器仅使用了这些值。
当选择了上下文菜单扩展添加的菜单项之一时,会调用 IContextMenu.InvokeCommand
。上下文菜单响应此方法执行或启动所需的动作。
将处理程序注册到特定文件类
上下文菜单处理程序与文件类或文件夹关联。对于文件类,处理程序在以下子项下注册。
HKEY_CLASSES_ROOT\<File Type>\shellex\ContextMenuHandlers
上下文菜单处理程序的注册在 FileContextMenuExt
的 Register
方法中实现。附加到该方法的 ComRegisterFunction
属性使基本 COM 类的注册之外的用户编写代码得以执行。Register 调用 ShellExtLib.cs 中的 ShellExtReg.RegisterShellExtContextMenuHandler
方法将处理程序与特定文件类型关联。如果文件类型以 '.' 开头,它会尝试读取 HKCR\<File Type>
键的默认值,该值可能包含文件类型链接到的程序 ID。如果默认值不为空,则使用程序 ID 作为文件类型继续注册。
例如,此代码示例将处理程序与“.cs”文件关联。在安装 Visual Studio 2010 时,HKCR\.cs 默认的默认值为“VisualStudio.cs.10.0
”,因此我们继续在 HKCR\VisualStudio.cs.10.0\ 下注册处理程序,而不是在 HKCR\.cs 下注册。在示例处理程序的注册过程中,会添加以下项和值。
HKCR
{
NoRemove .cs = s 'VisualStudio.cs.10.0'
NoRemove VisualStudio.cs.10.0
{
NoRemove shellex
{
NoRemove ContextMenuHandlers
{
{B1F1405D-94A1-4692-B72F-FC8CAF8B8700} =
s 'CSShellExtContextMenuHandler.FileContextMenuExt'
}
}
}
}
注销在 FileContextMenuExt
的 Unregister
方法中实现。与 Register
方法类似,附加到该方法的 ComUnregisterFunction
属性使在注销过程中执行用户编写的代码成为可能。它会移除 HKCR\<File Type>\shellex\ContextMenuHandlers 下的 {<CLSID>}
项。
下载
请访问 http://1code.codeplex.com 下载最新的代码示例。