Windows Shell扩展系列文章 1 - .NET 4 编写Windows Shell上下文菜单扩展
2011-03-23 09:37 Jialiang 阅读(3270) 评论(4) 编辑 收藏 举报
在MSDN论坛,大量的开发人员经常问道这样一个问题:
如何编写.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问世之前,使用.NET编写进程内的Windows Shell 扩展是不被支持的。开发人员不得不使用native C++进行编写。原因是.NET 4之前的CLR只允许一个版本的CLR运行在同一进程内。CLR项目经理Jesse Kapan在此论坛帖中对这个问题有详细阐述。
随着.NET 4引入了CLR in-process side-by-side特性,使用.NET 4或未来更高版本.NET编写Windows Shell扩展变成了可能。在.NET 4中CLR支持下列情况的In-Proc SxS:
1. v2.0和v4.0共存
2. v1.1和v4.0共存
而V1.1和V2.0则是不能够被同时加载到进程中。也就是说,进程中<4.0的CLR只能存在一个实例,这样做的原因非常简单:<4.0的CLR版本本身是不支持In-Proc SxS的,也就是说v1.1和v2.0一旦在同一个进程内加载是会出现各种各样的问题的。并且,我们不希望因为要支持SxS而去修改v1.1和v2.0,这样做的代价太大,同时也会把整个问题域变得更加复杂,因此最后决定不支持<4.0的CLR多于一个实例。当然了,>=4.0的CLR是可以多个并存的,也就是说V4.0,V5.0,v6.0,等等,都是可以和平共处在同一个进程内。原因很简单,>4.0的CLR是In-Proc SxS Aware的。
解释了那么多理论的东西,那我究竟该如何编写.NET代码开发Windows Shell 扩展呢?
本系列文章将逐一为你介绍使用.NET开发Windows Shell 上下文菜单扩展,Icon扩展,Drag-and-drop扩展,缩略图扩展,Icon overlay扩展等的详细开发方法和示例。
从最常见的Windows Shell 上下文扩展开始说起。
示例代码 (示例代码下载)
CSShellExtContextMenuHandler: Shell context menu handler (C#)
VBShellExtContextMenuHandler: Shell context menu handler (VB.NET)
CppShellExtContextMenuHandler: Shell context menu handler (C++)
示例演示
当你在Visual Studio 2010中成功编译CSShellExtContextMenuHandler示例,你会得到CSShellExtContextMenuHandler.dll。随后,如你的操作系统是x86的,编译并安装CSShellExtContextMenuHandlerSetup(x86)。如你的操作系统是x64的,编译并安装CSShellExtContextMenuHandlerSetup(x64)。
安装完后,找到任何一个.cs文件。在Windows Explorer中右键该文件。你会看到 “Display File Name (C#)” 上下文菜单项。该菜单项就是由我们的上下文菜单扩展示例添加的。点击该菜单项,你会看到一个消息框显示该.cs文件的完整路径。
实现细节
A. 创建和设置工程
在Visual Studio 2010中, 创建一个名为"CSShellExtContextMenuHandler"的Visual C# / Windows / Class Library 工程, 在签名页上,使用强名称密钥文件对该程序集进行签名.
-----------------------------------------------------------------------------
B. 执行一个基本组件对象模型 (COM) DLL
所有扩展应用程序都是以COM对象的方式在进程内运行.
创建一个基本的.NET COM对象是很简单的事情.您只需要定义一个公共类该类的ComVisible属性设置为true,
使用Guid属性设置一个CLSID,显式实现某些COM接口.例如,
[ClassInterface(ClassInterfaceType.None)]
[Guid("B1F1405D-94A1-4692-B72F-FC8CAF8B8700"), ComVisible(true)]
public class SimpleObject : ISimpleObject
{
... // 实现接口
}
您甚至不需要自己实现 IUnknown 和类工厂,因为.net 框架已经为您处理好了.
-----------------------------------------------------------------------------
C. 使用上下文菜单应用程序并关联一个文件类型
-----------
上下文应用程序的实现:
FileContextMenuExt.cs 文件定义上下文菜单处理程序. 必须继承IShellExtInit and IContextMenu
接口. 在文件ShellExtLib.cs中你可以看到接口可以通过COMImport属性导入
[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)
{
...
}
}
COM操作使得具有最终输出参数的函数看起来是由它返回的该值,PreserveSig属性用于关闭这一特性。 当您不设置(例如,GetCommandString 方法 PreserveSigAttributeIContextMenu)失败时候 ,将引发一个.NET 的异常。
例如 Marshal.ThrowExceptionForHR(WinError.E_FAIL) ;在 PreserveSigAttribute 应用于托管的方法的签名时,属性化方法的托管和非托管签名是相同的 (例如
QueryContextMenu 方法的 IContextMenu)。保留原始的方法,如果该成员返回多个成功的 HRESULT值签名是必要的,并且您想要检测不同的值。
只有该上下文应用程序被成功注册了,当上下文菜单显示的时候才能被实例化.
1. IShellExtInit接口实现
当上下文扩展COM对象被实例化时,IShellExtInit::Initialize方法将被调用.
IShellExtInit::Initialize 提供与在 CF_HDROP 格式中保存一个或多个文件名称的IDataObject 对象的上下文菜单扩展。所选的文件和文件夹通过 IDataObject 对象,您可以枚举。如果 IShellExtInit::Initialize 返回的是S_OK 以外的其他任何值则不能使用上下文菜单扩展.
在示例代码中, FileContextMenuExt::Initialize 枚举被选中的文件和文件夹. 只有一个文件被选中的时候, 这个方法将保存文件名并返回S_OK供后续处理. 如果没有文件或不止一个文件被选中则函数返回E_FAIL您将不能使用这个上下文应用程序.
2. IContextMenu接口实现:
当IShellExtInit::Initialize返回iS_OK后, IContextMenu::QueryContextMenu这个方法将被调用用以获取子菜单项或添加子菜单项. QueryContextMenu方法的实现是非常简单的.上下文扩展使用InsertMenuItem或类是的函数插入子菜单项. 菜单标示符ID必须大于第一个菜单标示符ID且小于最后一个菜单标示符ID. QueryContextMenu必须返回可用的最大的标识符ID并加一的标识符ID. 指定菜单命令标识符的最佳方法是在序列中从0开始.如果上下文菜单扩展无需添加菜单项则QueryContextMenu返回0.
在示例代码中, 我们插入一个 "Display File Name (C#)"子菜单项并在它的下面加一个分隔符.
IContextMenu::GetCommandString方法被用来检索并返回子菜单项的文本, 例如,为菜单项显示帮助文本. 如果用户选中了上下文菜单中添加的子菜单,应用程序的IContextMenu::GetCommandString 方法将被调用来获取帮助文本并显示在资源管理器的状态栏上ANSI或Unicode的字符集都可以被使用. 示例程序只使用Unicode的uFlags参数, 因为自Windows 2000以后资源管理器只接受Unicode的字符集.
当某个通过上下文菜单扩展安装的子菜单项被选中时,IContextMenu::InvokeCommand的方法在上下文菜单中执行或激发响应此方法所需的操作.
-----------
注册为某一类文件的处理程序:
上下文菜单处理程序关联的类文件或文件夹.
文件类, 程序将被注册在.
HKEY_CLASSES_ROOT\<File Type>\shellex\ContextMenuHandlers
上下文菜单处理程序的注册是在FileContextMenuExt方法中实现.ComRegisterFunction属性附加到该方法使基本以外的其他用户编写代码的执行注册的 COM 类。注册调用,ShellExtReg.RegisterShellExtContextMenuHandler 方法中,ShellExtLib.cs
将该处理程序与特定文件类型相关联。如果文件的类型是以'.'开头的,它会尝试读取 HKCR\ < 文件类型 > 键的可能的默认值包含链接的文件类型的程序 ID。如果默认值不为空,使用作为文件类型的程序 ID 进行注册。
例如, 示例文件关联了 '.cs' 类型的文件.
如果您安装了Visual Studio 2010则注册表项HKCR\.cs下就有了一个默认的文件类型'VisualStudio.cs.10.0',所以将使用'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函数中被实现并执行,类似注册的方法, ComUnregisterFunction属性附加到该方法使基本以外的其他用户编写代码的执行
注销的COM 类,执行后将删除注册表项HKCR\CLSID\{<CLSID>} 键 {<CLSID>}和HKCR\<File Type>\shellex\ContextMenuHandlers下的值.
下载
http://1code.codeplex.com/releases
下载后,在Visual Studio 2010目录下找到CS/VB/CppShellExtContextMenuHandler 示例。
如你有任何反馈或问题,欢迎通过onecode@microsoft.com联系我们。