【转】Windows Shell扩展编程傻瓜手册大全:上下文菜单扩展

简介:
引用自: http://blog.163.com/yesaidu@126/blog/static/51819307200861853827582/

Part I: A step-by-step tutorial on writing shell extensions

第一节:Windows shell扩展初步:上下文菜单扩展

 

作者:Michael Dunn

译者:yesaidu

 

源代码下载:1       2

 

目录

 README

 系列绪言

 第一部分绪言

 AppWizard开始

 初始化接口

 上下文菜单交互接口

 更改上下文菜单

 在状态栏显示拉线式(fly-by)帮助

 执行用户选择

 其他代码细节

 注册Shell扩展

 调试Shell扩展

 所有的外观

 版权与许可

 修订历史

 

 

README

         我想,你在行动之前,或者你在本手册的讨论板发帖之前应该阅读这份材料。

         本手册最初是用VC 6编写的。现在,VC8都出来了,我感觉是时候对本手册进行升级到VC7.1了。(通过VC7.1自动升级VC6项目,并不一定会完全地完成代码转换;因此,VC7.1用户可能碰到这样的现象,即在转换、编译示例代码后,运行时可能没有效果或出错。)只要我仔细检查并更新本手册,本手册将体现VC7.1的新特点。我将会提供VC7.1项目的源码下载。

         VC2005用户要注意了:VC2005体验版(Express edition没有一同发布ATLMFC。既然本手册用到了ATL,有时还使用了MFC,因此,你不能用VC2005体验版来编译示例代码。

         如果你正使用VC6,那么,你应该设法取得最新的平台SDK。你可以使用WEB安装版web install version),或者下载CAB文件或者ISO镜像包,安装它们到本地。确认把SDKINCLUDELIB目录添加到了VC的搜索路径中。你能在PSDK程序组中找到Visual Studio Registration目录。这是一个好主意,无论你使用VC7,还是用VC8,你都能取得最新的PSDK头文件和库文件。
Windows Shell扩展编程傻瓜手册大全:上下文菜单扩展 - yesaidu - yesaidu的博客

VC7用户注意了:如果你没有更新PSDK必须改变默认的INCLUDE路径。确信“VC++目录”-“包含文件”列表的第一项是$(VCInstallDir)PlatformSDK\include,它在($VCInstallDir)include前面,如下图:
Windows Shell扩展编程傻瓜手册大全:上下文菜单扩展 - yesaidu - yesaidu的博客

         由于一直没有使用过VC 8,因此我不确定示例代码在VC 8上是否可以通过编译。只是希望,把VC7项目升级到VC8的自动转换功能比从VC6VC7的要好些。如果你使用VC8编译示例时遇到了任何疑惑,请在讨论板发帖。

 

 

手册绪言

         所谓shell扩展就是能增加某些功能到Windows资源管理器的COM对象。Shell扩展有很多内容,但关于它们的文档资料却非常少见。(自从我最先发表这份手册的六年来,我相信情况要好多了。)如果你想深入Windows shell的内部,极力推荐Dino Esposito的巨作Visual C++ Windows Shell Programming (ISBN 1861001843)。对于没有这本书的人,或者仅仅对shell 扩展感兴趣的朋友,我将给你一个惊喜:一本有关shell 扩展编程的傻瓜手册。即使本手册并未让你感到惊喜,那么,对你理解如何编写shell扩展也会提供很好的帮助。本手册假定你理解并掌握了COMATL的基本原理和应用。如果你还需要学习COM基本原理,请参考Intro to COM

         第一节介绍了shell扩展的概要,并提供了一个上下文菜单扩展的示例,使你对后面的章节充满兴趣。

         从字面上看,shell扩展包括两个方面:shell和扩展。所谓shell,就是资源管理器Explorer;而扩展就是指在预定的事件发生时由Explorer调用执行的代码(比如,在.DOC文件上右击)。因此,shell扩展就是为Explorer增添功能的COM对象。

         shell扩展是一个进程内服务器,它实现了跟Explorer通信的接口。ATL是设计一个shell扩展,并使之运行的最简单办法;这样你就不用为一遍又一遍的编写QueryInterface()AddRef()而大伤脑筋。在Windows NT下调试shell扩展要更容易些,这点,我在后面还会谈到。

         Shell扩展有很多种类型,每一类型都有其被调用的时机:即每种类型在不同的事件发生时被调用执行。下表列出了一些较常见的类型,以及它们被调用的情况:

类型

被调用的时机

它可以做什么

Context menu扩展处理器

用户在文件对象或文件夹对象或目录窗口背景(需要shell v 4.71+以上)单击右键

在上下文菜单中添加菜单项

Property sheet扩展处理器

文件属性对话框显示时

在属性对话框中定制属性页

Drag and drop扩展处理器

用户用右键拖放文件到文件夹窗口或桌面时

在上下文菜单中添加菜单项

Drop handler扩展处理器

用户拖对象并将其放到文件上时

任何你想做的

QueryInfo 扩展处理器 
(
需要shell version 4.71+)

用户在文件、“我的电脑”等其他shell对象的图标上悬停时

返回一个Explorer显示在工具提示中的字符串


 

 

 

第一节绪言

         现在,你可能有很多的疑问:为什么扩展看起来像Explorer?它到底是什么样的?一个例子就是WinZip(或者WinRAR,我没安装WinZip ^_^――译者)——它包含了多种shell扩展,其中之一就是上下文扩展。下图是WinZip(其实是WinRA ^_^――译者)为压缩文件在上下文菜单中添加的菜单项:

Windows Shell扩展编程傻瓜手册大全:上下文菜单扩展 - yesaidu - yesaidu的博客

         WinZip编写了增加菜单项的代码,提供了Explorer状态栏上的菜单项帮助提示(fly-by help),并在用户选择一个WinZip菜单命令时执行相应的操作。

         WinZip还提供了拖曳扩展处理,此类型跟上下文菜单扩展非常相似,但它是在用户通过右键拖曳文件时才被触发。下图是WinZip(也是WinRA ^_^――译者)拖曳文件弹出的菜单项:
Windows Shell扩展编程傻瓜手册大全:上下文菜单扩展 - yesaidu - yesaidu的博客

         还有很多的shell扩展类型,Microsoft不断向每一个新的Windows版本中增加更多的类型。现在,让我们把注意力放到上下文菜单扩展上,因为它易于编写,效果也很明显(能够立即让你满意)。

         在动手编码之前,有一些便于编码和调试的小技巧:当Explorer调用shell扩展(由用户触发)后,shell扩展暂时驻于内存中;此时,你无法重新编译此扩展的DLL文件。为让Explorer更迅速卸载扩展,可以在注册表中创建下面的键:

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Explorer\AlwaysUnloadDLL

并设置其默认值为“1”。在Win9x平台上,这是最好的办法。在WinNT上,可以在下面的键

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer

创建一个DWORD DesktopProcess,也设置它的值为1。(译者:如下图,Win9x系统太少见了)这使得“桌面”和“任务栏”运行于一个进程,其他的Explorer窗口运行在其独立进程。这意味着,你可以调试单个Explorer窗口,当你关闭该窗口时,相关的扩展DLL就会被自动卸载,这样就避免了DLL文件正被Windows使用而无法替换的问题。要使注册表修改生效,需要注销后重新登录。

Windows Shell扩展编程傻瓜手册大全:上下文菜单扩展 - yesaidu - yesaidu的博客

         稍后,我将说明Win9x下如何进行调试。

 

 

使用AppWizard开始

         我们先做一个简单的扩展,它仅仅弹出一个消息框以表明工作正常。我们把它关联到文本文件,这样,当我们在一个文本文件上右击时,该扩展就会被调用。

         好了,让我们开始吧!什么?我还没有告诉你如何使用那些神秘的shell扩展接口?别着急,我会边进行边解释。我觉得,给出一个概念,紧跟着一个示例代码,这样做有助于理解。当然,我也可以先解释所有的概念,然后列出示例代码,不过这样很难吸引注意力。不管怎样,开启你的VC,我们要开始了。

         运行AppWizard,生成一个名为“SimpleExt”的ATL COM工程:

Windows Shell扩展编程傻瓜手册大全:上下文菜单扩展 - yesaidu - yesaidu的博客去掉属性化,保留其它默认选项,点击“完成”。
Windows Shell扩展编程傻瓜手册大全:上下文菜单扩展 - yesaidu - yesaidu的博客
现在,我们有了一个空的ATL项目,它可以编译生成一个DLL,但我们还需要添加shell扩展COM对象。在类视图中,右击“SimpleExt”项,选择“新建ATL 对象” VC7,选择“添加”→“添加类”,下图。本文的环境是Windows XPVC7.1,因此附图都是VC7的)。

Windows Shell扩展编程傻瓜手册大全:上下文菜单扩展 - yesaidu - yesaidu的博客

         ATL对象向导中,第一页已经选择了“简单对象”,点击“下一步”。
Windows Shell扩展编程傻瓜手册大全:上下文菜单扩展 - yesaidu - yesaidu的博客

在第二页,在“简称”编辑框中输入“SimpleShlExt”(其他编辑框会自动完成):
Windows Shell扩展编程傻瓜手册大全:上下文菜单扩展 - yesaidu - yesaidu的博客

         在默认情况下,向导将创建以C和脚本客户端为基础的OLE自动化兼容的COM对象。我们的扩展仅仅由Explorer调用,因此我们去掉自动化支持。在“属性”页,选择“接口”类型为“自定义”,并且选择“聚合”为“否”:
Windows Shell扩展编程傻瓜手册大全:上下文菜单扩展 - yesaidu - yesaidu的博客

         点击“完成”,就创建了CSimpleShlExt类,它包含了实现COM对象的最基本代码。我们将向这个类加入代码。

 

 

初始化接口

         当我们的shell扩展被加载时,Explorer将调用QueryInterface()函数,以取得IShellExtInit接口指针。该接口仅有一个方法Initialize(),其函数原型如下:

HRESULT IShellExtInit::Initialize (

                               LPCITEMIDLIST pidlFolder,

                               LPDATAOBJECT pDataObj,

                               HKEY hProgID )

Explorer通过该方法向我们传递各种各样的信息。pidlFolder是用户所操作文件所在的文件夹的PIDLPIDL [pointer to an ID list],指向ID列表的指针,是一个数据结构,它唯一标识了在shell空间的任何对象,这个对象是或者不是文件系统的对象。) pDataObj是一个IDataObj接口变量,通过它可以取得用户正操作的文件名。hProgID是一个HKEY接口变量,通过它可以取得扩展DLL的注册信息。在本例中,仅仅需要pDataObj参数。

         要添加这一方法到我们的COM对象,打开文件SimpleShlExt,加入下列粗体的行。AppWizard生成了一些不必需的COM关系代码,既然我们不实现我们自己的接口,所以我指出这些失败的能被移除的代码(带删除线的那些):

       #include <shlobj.h>

       #include <comdef.h>

 

       class ATL_NO_VTABLE CSimpleShlExt :

          public CComObjectRootEx<CComSingleThreadModel>,

          public CComCoClass<CSimpleShlExt, &CLSID_SimpleShlExt>,

          public ISimpleShlExt,

          public IShellExtInit

       {

          BEGIN_COM_MAP(CSimpleShlExt)

              COM_INTERFACE_ENTRY(ISimpleShlExt)

              COM_INTERFACE_ENTRY(IShellExtInit)

          END_COM_MAP()

COM_MAPATL实现QueryInterface的宏,它告诉ATL其它程序能从COM对象取得哪些接口。

         在类声明中,加入Initialize函数。此外,还需要一个变量来保存文件名:

       protected:

           TCHAR m_szFile[MAX_PATH];

 

       public:

           // IShellExtInit

           STDMETHODIMP Initialize(LPCITEMIDLISTLPDATAOBJECTHKEY);

接着,在文件SimpleShlExt.cpp中,添加Initialize的实现代码:

       STDMETHODIMP CSimpleShlExt::Initialize (

                                          LPCITEMIDLIST pidlFolder,

                                          LPDATAOBJECT pDataObj,

                                          HKEY hProgID)

         我们要做的是取得右键单击选中的文件名,并把它显示在消息框中。如果选中了多个文件,可以通过pDataObj接口指针来访问它们,不过为了保持例子的简单,我们只获取第一个文件名。

         文件名的格式和拖曳文件到WS_EX_ACCEPTFILES风格的窗口是的文件名格式一致,这样说来,我们可以通过同样的APIDragQueryFile来取得文件名。我们先取得包含在IDataObject中的数据句柄:

       void CSimpleShlExt::Initialize (LPCITEMIDLIST pidlFolderLPDATAOBJECT pDataObjHKEY hProgID)

       {

           FORMATETC fmt = { CF_HDROPNULLDVASPECT_CONTENT, -1, TYMED_HGLOBAL };

           STGMEDIUM stg = { TYMED_HGLOBAL };

           HDROP      hDrop;

 

           // 在数据对象内查找CF_HDROP类型数据。

           // 如果没有数据,返回一个错误(“无效参数”)给Explorer

           if ( FAILEDpDataObj->GetData ( &fmt, &stg ) ))

              return E_INVALIDARG;

 

           // 取得指向实际数据的指针。

           hDrop = (HDROPGlobalLock ( stg.hGlobal );

 

           // 确保非NULL

           if ( NULL == hDrop )

              return E_INVALIDARG;

         注意,错误检查是极其重要的,尤其是对指针的检查。因为我们的扩展运行于Explorer进程空间,如果我们的程序挂了,Explorer会跟着挂。在Win9x系统上,这样的崩溃可能导致需要重启系统。

         现在,我们有了HDROP句柄,可以取得所需的文件名了:

           // 有效性检查,至少有一个文件名

           UINT uNumFiles = DragQueryFile ( hDrop, 0xFFFFFFFF, NULL, 0 );

           HRESULT hr = S_OK;

 

           if ( 0 == uNumFiles )

           {

              GlobalUnlock ( stg.hGlobal );

              ReleaseStgMedium ( &stg );

              return E_INVALIDARG;

           }

 

           // 取得第一个文件名,保存到 m_szFile

           if ( 0 == DragQueryFile ( hDrop, 0, m_szFileMAX_PATH ) )

              hr = E_INVALIDARG;

 

           GlobalUnlock ( stg.hGlobal );

           ReleaseStgMedium ( &stg );

 

           return hr;

}

如果返回E_INVALIDARGExplorer不会在右键事件时再调用我们的扩展。如果返回S_OKExplorer将再次调用QueryInterface(),以取得另一接口:IContextMenu

 

 

与上下文菜单交互的接口

         一旦Explorer初始化了我们的扩展,它将调用IContextMenu方法来增加菜单项、提供状态栏帮助(fly-by help),以及响应用户的选择。

         添加IContextMenu接口与IShellExtInit相类似。打开文件SimpleShlExt.h,加入下列加粗的行:

class ATL_NO_VTABLE CSimpleShlExt :

             public CComObjectRootEx<CComSingleThreadModel>,

             public CComCoClass<CSimpleShlExt, &CLSID_SimpleShlExt>,

             public IShellExtInit,

               public IContextMenu

       {

          BEGIN_COM_MAP(CSimpleShlExt)

              COM_INTERFACE_ENTRY(IShellExtInit)

COM_INTERFACE_ENTRY(IContextMenu)

          END_COM_MAP()

接着,添加IContextMenu方法的函数原型:

       public:

           // IContextMenu

           STDMETHODIMP GetCommandString (UINTUINTUINT*, LPSTRUINT);

           STDMETHODIMP InvokeCommand (LPCMINVOKECOMMANDINFO);

           STDMETHODIMP QueryContextMenu (HMENUUINTUINTUINTUINT);

 

 

修改上下文菜单

         IContextMenu有三个方法。第一个QueryContextMenu()修改上下文菜单。它的原型如下:

       HRESULT IContextMenu::QueryContextMenu (

                                          HMENU hmenu,

                                          UINT uMenuIndex,

                                          UINT uidFirstCmd,

                                          UINT uidLastCmd,

                                          UINT uFlags );

hmenu是上下文菜单句柄。uMenuIndex是我们要添加菜单项的开始位置。uidFirstCmd uidLastCmd是菜单命令ID值范围。uFlags表明Explorer调用QueryContextMenu()的缘由,这个后面还会谈到。

         关于此方法的返回值,你翻阅不同的文档,可能得到不同的答案。Dino Esposito在他的书中认为这个返回值是所添加的菜单项的数目。但VC6MSDN却说它是最后一个菜单项的命令ID1;然而,联机MSDN却说:

如果函数成功,返回的HRESULT值就是分配的菜单项命令ID的最大差值加1。例如,uidFirstCmd5,你添加了3个菜单项,它们的命令ID分别是578。那么,返回值应该是MAKE_HRESULT(SEVERITY_SUCCESS,  0,  8 - 5 + 1)。否则,返回一个OLE错误。

         一直以来,我都按照Dino的解释来编写代码,这些代码工作地很好。实际上,他的解释与联机MSDN是一致的,只要将uidFirstCmd作为第一项菜单项ID,后续的菜单项依次累加1

         我们这里的扩展简单的加入一个菜单项,因此QueryContextMenu()函数非常简单:

 

       STDMETHODIMP CSimpleShlExt::QueryContextMenu (

           HMENU hmenuUINT uMenuIndexUINT uidFirstCmd,

           UINT uidLastCmdUINT uFlags )

       {

           // 如果标识包含了 CMF_DEFAULTONLY,那么,我们啥都不做

           if ( uFlags & CMF_DEFAULTONLY )

              return MAKE_HRESULT ( SEVERITY_SUCCESSFACILITY_NULL, 0 );

 

           InsertMenu ( hmenuuMenuIndexMF_BYPOSITIONuidFirstCmd_T("简单SHELL扩展测试") );

 

           return MAKE_HRESULT ( SEVERITY_SUCCESSFACILITY_NULL, 1 );

       }

         首先,我们检查uFlags的值。在MSDN内,你能找到所有的标识和它们的解释,但对于上下文菜单扩展而言,仅仅一个值是有意思的:即CMF_DEFAULTONLY。该标识告诉shell命名空间扩展保留默认的菜单项;(如果设置了它的话,)shell扩展将不增加任何的菜单项。这也是我们为什么返回0的原因。如果未设置它,我们就可以通过句柄hmenu来修改菜单,并返回1告诉shell增加了一个菜单项。

 

 

在状态栏显示提示帮助(fly-by help

         下一个要被调用的IContextMenu方法是GetCommandString()。当用户在Explorer窗口中右击文本文件,或者选中文本文件后点击“文件”菜单,鼠标指到我们添加的菜单项时,状态栏将显示提示信息。GetCommandString()函数返回一个字符串供Explorer显示。

         GetCommandString()原型如下:

       HRESULT IContextMenu::GetCommandString (

                            UINT idCmdUINT uFlagsUINTpwReserved,

                            LPSTR pszNameUINT cchMax );

idCmd是基于0的计数器,它表明了被选中的菜单项。由于我们只有一个菜单项,所以idCmd总为0。不过,如果我们添加了,比如说,3个菜单项,idCmd就是012uFlags是另外的一组标识,这个留待后面再讨论。pwReserved可以被忽略。pszNameshell所有的缓冲区,用于显示的帮助信息将拷贝到它中。cchMax是上述缓冲区的尺寸。返回值是HRESULT常量,比如说S_OKE_FAIL

         GetCommandString()也能用来取得菜单项的“动作”(verb)。动作是与语言无关的串,它标识了能作用于文件对象的动作。关于这点,ShellExecute()的文档中有更详细的说明;有关动作的内容最好留待另外的文章(可以就这方面的内容另外一篇文章),这里简要的说,是列在注册表中的动作(比如“打开”和“打印”),或者有上下文菜单扩展动态创建的动作。这可以通过ShellExecute()来调用shell扩展的动作。

         总之,我7li8li的说了这么多,就是为了解释清楚GetCommandString()的作用。如果Explorer要提示信息,我们就给它;如果Explorer请求一个动作,就忽视它。这就是uFlags的作用。如果uFlags设置了GCS_HELPTEXT位,Explorer请求提示信息。另外,如果uFlags设置了GCS_UNICODE,我们必须给它一个UNICODE串。

         本例中GetCommandString()如下:

       #include <atlconv.h>  // ATL串转换宏

 

       STDMETHODIMP CSimpleShlExt::GetCommandString (

           UINT idCmdUINT uFlagsUINTpwReservedLPSTR pszNameUINT cchMax )

       {

           USES_CONVERSION;

 

           // 由于这里只有一个菜单项,所以idCmd 必须为0

           if ( 0 != idCmd )

              return E_INVALIDARG;

 

           // 如果Explorer请求提示信息,拷贝串到提供的缓冲区

           if ( uFlags & GCS_HELPTEXT )

           {

              LPCTSTR szText = _T("简单的SHEEL扩展帮助(fly-by help)");

 

              if ( uFlags & GCS_UNICODE )

              {

                  // 这里,需要把 pszName 转换为 UNICODE

                  lstrcpynW ( (LPWSTRpszNameT2CW(szText), cchMax );

              }

              else

              {

                  // ANSI版本

                  lstrcpynA ( pszNameT2CA(szText), cchMax );

              }

 

              return S_OK;

           }

 

           return E_INVALIDARG;

       }

         这没什么奇怪的;我使用了硬编码并将它转换为合适的字符集。如果你从没用过ATL转换宏,你最好去学一下。它们在你向COM方法和OLE函数传递参数时,十分有用。

         一个需要注意的重要事项是,lstrcpyn()函数保证字符串是以NULL结束的。这和CRTC运行时)函数strncpy()不同,后者在源串的长度大于等于cchMax时并不在串最后插入NULL。我建议总是使用lstrcpyn(),这样就无需在调用strncpy()后总是检查以确保串是否以NULL结束。

 

 

执行用户的选择

         IContextMenu接口最后的方法是InvokeCommand()。此方法在用户点击我们增加的菜单项后被调用,其函数原型如下:

       HRESULT IContextMenu::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo );

结构CMINVOKECOMMANDINFO9个成员,就我们的目的而言,仅仅需要关注lpVerbhwndslpVerb有两个用途:它既可以是被引发的动作名,也可以是被点击的菜单向索引。hwnds是用户引发我们的扩展时所在的Explorer窗口句柄;我们可以将其作为我们用来显示信息的窗口的父窗口。

         由于我们只有一个菜单项,所以只需要检查lpVerb:如果它为0,那么我们的菜单项就被选中了。最简单的事情是弹出消息框,这里的代码也就能干这事儿。消息框显示了被选中的文件名,这表明代码工作正常。

       STDMETHODIMP CSimpleShlExt::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo )

       {

           // 如果 lpVerb 指向一个实际串,忽略此次调用并退出

           if ( 0 != HIWORDpCmdInfo->lpVerb ) )

              return E_INVALIDARG;

 

           // 取得命令索引,这里,唯一有效的值为0

           switch ( LOWORDpCmdInfo->lpVerb) )

           {

           case 0:

              {

                  TCHAR szMsg [MAX_PATH + 32];

 

                  wsprintf ( szMsg_T("被选中的文件:\n\n%s"), m_szFile );

 

                  MessageBox ( pCmdInfo->hwndszMsg_T("SimpleShlExt"),

                     MB_ICONINFORMATION );

 

                  return S_OK;

              }

              break;

 

           default:

              return E_INVALIDARG;

              break;

           }

       }

 

 

其它代码细节

         这里,集中说明如何移除AppWizard生成的多余的OLE自动化特性方面的代码。首先,可以移除SimpleShlExt.rgs(这个文件的用途在下一节详述)中些注册表入口:

    HKCR

    {

       SimpleExt.SimpleShlExt.1 = s 'SimpleShlExt Class'

       {

           CLSID = s '{1CE1EBEB-1254-4880-B807-809CC31E8D2C}'

       }

       SimpleExt.SimpleShlExt = s 'SimpleShlExt Class'

       {

           CLSID = s '{1CE1EBEB-1254-4880-B807-809CC31E8D2C}'

           CurVer = s 'SimpleExt.SimpleShlExt.1'

       }

       NoRemove CLSID

       {

           ForceRemove {1CE1EBEB-1254-4880-B807-809CC31E8D2C} = s 'SimpleShlExt Class'

           {

              ProgID = s 'SimpleExt.SimpleShlExt.1'

              VersionIndependentProgID = s 'SimpleExt.SimpleShlExt'

              InprocServer32 = s '%MODULE%'

              {

                  val ThreadingModel = s 'Apartment'

              }

              val AppID = s '%APPID%'

              'TypeLib' = s '{172391D4-B01E-4EF5-AC3E-34C99889D8B0}'

           }

       }

    }

         我们也能移除DLL资源中的类型库。(VC7)在“资源视图”中,选中“SimpleExt.rc”,右击,选中“资源包括”: 
Windows Shell扩展编程傻瓜手册大全:上下文菜单扩展 - yesaidu - yesaidu的博客

在“资源包括”对话框的“编译时指令”中有一行类型库包括:
Windows Shell扩展编程傻瓜手册大全:上下文菜单扩展 - yesaidu - yesaidu的博客

移除这行,VC弹出警告,点击“确定”:
Windows Shell扩展编程傻瓜手册大全:上下文菜单扩展 - yesaidu - yesaidu的博客

         移除类型库后,我们还需要修改两处代码,以告诉ATL,它不应通过类型库来处理。在文件SimpleExt.cpp中的DllRegisterServer()/DllUnregisterServer()函数,设置RegisterServer()/UnregisterServer()的参数为。

       STDAPI DllRegisterServer (void)

       {

           // ...

           return _Module.RegisterServer(TRUE FALSE);

       }

STDAPI DllUnregisterServer (void)

       {

           // ...

           return _Module.UnregisterServer(TRUE FALSE);

       }

 

 

注册Shell扩展

         现在,我们实现所有的COM接口。不过,怎么才能让Explorer使用我们的扩展呢?ATL自动生成注册COMD服务器DLL 的代码,但那是给其它程序使用。为了让Explorer知道扩展存在,需要在文本文件的下述注册表键下注册我们的扩展:

         HKEY_CLASSES_ROOT\txtfile

在这个注册表键下,名为ShellEx的键保存了有关文本文件的shell扩展列表;在它的下一级,名为ContextMenuHandlers的键保存了上下文菜单扩展列表。每个扩展都拥有一个ContextMenuHandlers的子键,其默认值为shell扩展的GUID。本文简单扩展的示例,将创建如下子键:

         HKEY_CLASSES_ROOT\txtfile\ShellEx\ContextMenuHandlers\SimpleShlExt

并设置其默认值为扩展COMGUID,如“{1CE1EBEB-1254-4880-B807-809CC31E8D2C }”。

         不必编写代码来完成COM的注册。在“解决方案管理器”中有文件SimpleShlExt.rgs;这是一个文本文件,它被ATL解析,指导ATL在该服务器注册时添加哪些键,在卸载时删除哪些键。下面是注册该扩展所要添加的注册表键:

    HKCR

    {

       NoRemove txtfile

       {

           NoRemove ShellEx

           {

              NoRemove ContextMenuHandlers

              {

                  ForceRemove SimpleShlExt = s '{1CE1EBEB-1254-4880-B807-809CC31E8D2C}'

              }

           }

       }

    }

每一行都代表一个注册键名。“HKCR”是HKEY_CLASSES_ROOT的缩写。关键字NoRemove表明这个键在服务器卸载时不用删除。关键字ForceRemove表明在写入新键之前,如果该键存在,那么就要先删除它;这行剩下的部分指定了一个字符串(这就是“s”的意思),它是SimpleShlExt的默认值。

         这里,我插入几句。我们的扩展注册在HKEY_CLASSES_ROOT\txtfile下;然而,“txtfile”并不是持久的或者预先定好的名字。如果你查看一下HKEY_CLASSES_ROOT\.txt,它的默认值就是“txtfile”。这样,有两个副作用:

1、  既然“txtfile”可能不是正确的键名,因此RGS脚本并不可靠。

2、  某些文件编辑软件可能在安装到系统时,就把它自身关联到文本文件。如果它改变了HKEY_CLASSES_ROOT\.txt的默认值,那么所有的shell扩展就不能用了。

在我看来,这确实是一个设计上的漏洞。Microsoft可能也这么看,因为最新的扩展,如QueryInfo扩展就注册在HKEY_CLASSES_ROOT\.txt下。

       好了,到此为止。还有一个注册的细节,即在WinNT上,为了让非管理员帐号也能使用我们的扩展,得把它放到“approved ”扩展列表中:

         HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved

在这个键下,创建以扩展的GUID为名的串值,其内容任意。代码在DllRegisterServer()DllUnregisterServer()中,都是一些简单的注册表访问,我就不罗列了。你可以在示例代码中找到。

 

 

调试Shell扩展

         总有一天,你将写一个不是这么简单的shell扩展;那时,你不得不调试它。打开项目的属性对话框,在“调试”的“命令”编辑框输入“C:\windows\explorer.exe”。如果是WinNT系统,设置DesktopProcess键(前述),当你按F5时就启动了一个新的Explorer窗口。只要是在这个窗口完成所有的工作,那么在关闭这个窗口时,扩展就会被卸载,这样就不影响后面的重建DLL

         Win9x上,在运行调试器前,必须关闭shell:点击“开始”,点击“注销”。按下Ctlr+Alt+Shift,并点“取消”。这将关闭Explorer,任务栏(桌面)消失了。切换到MSVC,按F5开始调试。要中止调试,按下“Shift+F5”关闭Explorer。完成调试后,可以从“开始”“运行”“Explorer.exe”,让其正常启动。

 

扩展的外观

增加扩展后的上下文菜单项
Windows Shell扩展编程傻瓜手册大全:上下文菜单扩展 - yesaidu - yesaidu的博客

Explorer状态栏提示(fly-by help
Windows Shell扩展编程傻瓜手册大全:上下文菜单扩展 - yesaidu - yesaidu的博客

弹出的消息框,显示了所选中的文件名
Windows Shell扩展编程傻瓜手册大全:上下文菜单扩展 - yesaidu - yesaidu的博客

 

 

版权与许可

作者:Michael Dunn

译者:Yesaidu


相关文章
|
3月前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
|
3月前
|
Shell Linux
Linux shell编程学习笔记30:打造彩色的选项菜单
Linux shell编程学习笔记30:打造彩色的选项菜单
|
1月前
|
运维 监控 Shell
深入理解Linux系统下的Shell脚本编程
【10月更文挑战第24天】本文将深入浅出地介绍Linux系统中Shell脚本的基础知识和实用技巧,帮助读者从零开始学习编写Shell脚本。通过本文的学习,你将能够掌握Shell脚本的基本语法、变量使用、流程控制以及函数定义等核心概念,并学会如何将这些知识应用于实际问题解决中。文章还将展示几个实用的Shell脚本例子,以加深对知识点的理解和应用。无论你是运维人员还是软件开发者,这篇文章都将为你提供强大的Linux自动化工具。
|
2月前
|
Shell
Shell编程(下)
Shell编程(下)
111 1
|
2月前
|
Shell Linux Windows
Shell编程(上)
Shell编程(上)
52 1
|
2月前
|
Shell Linux Windows
Does windows have a shell layer?
Does windows have a shell layer?
|
2月前
|
Shell Linux 开发工具
|
2月前
|
监控 Unix Shell
shell脚本编程学习
【10月更文挑战第1天】shell脚本编程
84 12
|
3月前
|
Shell Linux
Linux shell编程学习笔记82:w命令——一览无余
Linux shell编程学习笔记82:w命令——一览无余
|
3月前
|
Shell Linux
Shell 编程 编写hello word
Shell 编写hello word
59 5