代码改变世界

Windows Shell扩展系列文章 1 - .NET 4 编写Windows Shell上下文菜单扩展

2011-03-23 09:37  Jialiang  阅读(3270)  评论(4编辑  收藏  举报

示例代码下载

 

在MSDN论坛,大量的开发人员经常问道这样一个问题:

如何编写.NET代码开发Windows Shell扩展?

在.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文件的完整路径。

 

image

 

实现细节

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联系我们。