引用自: 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)没有一同发布ATL或MFC。既然本手册用到了ATL,有时还使用了MFC,因此,你不能用VC2005体验版来编译示例代码。

如果你正使用VC6,那么,你应该设法取得最新的平台SDK。你可以使用WEB安装版(web install version),或者下载CAB文件或者ISO镜像包,安装它们到本地。确认把SDK的INCLUDE和LIB目录添加到了VC的搜索路径中。你能在PSDK程序组中找到Visual Studio Registration目录。这是一个好主意,无论你使用VC7,还是用VC8,你都能取得最新的PSDK头文件和库文件。

VC7用户注意了:如果你没有更新PSDK,必须改变默认的INCLUDE路径。确信“VC++目录”-“包含文件”列表的第一项是$(VCInstallDir)PlatformSDK\include,它在($VCInstallDir)include前面,如下图:

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

手册绪言

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

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

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

还有很多的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使用而无法替换的问题。要使注册表修改生效,需要注销后重新登录。

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

使用AppWizard开始

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

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

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

去掉属性化,保留其它默认选项,点击“完成”。

现在,我们有了一个空的ATL项目,它可以编译生成一个DLL,但我们还需要添加shell扩展COM对象。在类视图中,右击“SimpleExt”项,选择“新建ATL 对象” (VC7,选择“添加”→“添加类”,下图。本文的环境是Windows XP+VC7.1,因此附图都是VC7的)。

在ATL对象向导中,第一页已经选择了“简单对象”,点击“下一步”。

在第二页,在“简称”编辑框中输入“SimpleShlExt”(其他编辑框会自动完成):

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

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

初始化接口

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

HRESULT IShellExtInit::Initialize (

LPCITEMIDLIST pidlFolder,

LPDATAOBJECT pDataObj,

HKEY hProgID )

Explorer通过该方法向我们传递各种各样的信息。pidlFolder是用户所操作文件所在的文件夹的PIDL(PIDL [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_MAP是ATL实现QueryInterface的宏,它告诉ATL其它程序能从COM对象取得哪些接口。

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

protected:

TCHAR m_szFile[MAX_PATH];

public:

// IShellExtInit

STDMETHODIMP Initialize(LPCITEMIDLIST, LPDATAOBJECT, HKEY);

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

STDMETHODIMP CSimpleShlExt::Initialize (

LPCITEMIDLIST pidlFolder,

LPDATAOBJECT pDataObj,

HKEY hProgID)

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

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

void CSimpleShlExt::Initialize (LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hProgID)

{

FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };

STGMEDIUM stg = { TYMED_HGLOBAL };

HDROP      hDrop;

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

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

if ( FAILED( pDataObj->GetData ( &fmt, &stg ) ))

return E_INVALIDARG;

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

hDrop = (HDROP) GlobalLock ( 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_szFile, MAX_PATH ) )

hr = E_INVALIDARG;

GlobalUnlock ( stg.hGlobal );

ReleaseStgMedium ( &stg );

return hr;

}

如果返回E_INVALIDARG,Explorer不会在右键事件时再调用我们的扩展。如果返回S_OK,Explorer将再次调用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 (UINT, UINT, UINT*, LPSTR, UINT);

STDMETHODIMP InvokeCommand (LPCMINVOKECOMMANDINFO);

STDMETHODIMP QueryContextMenu (HMENU, UINT, UINT, UINT, UINT);

 

修改上下文菜单

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在他的书中认为这个返回值是所添加的菜单项的数目。但VC6的MSDN却说它是最后一个菜单项的命令ID加1;然而,联机MSDN却说:

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

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

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

STDMETHODIMP CSimpleShlExt::QueryContextMenu (

HMENU hmenu, UINT uMenuIndex, UINT uidFirstCmd,

UINT uidLastCmd, UINT uFlags )

{

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

if ( uFlags & CMF_DEFAULTONLY )

return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 0 );

InsertMenu ( hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd, _T("简单SHELL扩展测试") );

return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_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 idCmd, UINT uFlags, UINT* pwReserved,

LPSTR pszName, UINT cchMax );

idCmd是基于0的计数器,它表明了被选中的菜单项。由于我们只有一个菜单项,所以idCmd总为0。不过,如果我们添加了,比如说,3个菜单项,idCmd就是0、1、2。uFlags是另外的一组标识,这个留待后面再讨论。pwReserved可以被忽略。pszName是shell所有的缓冲区,用于显示的帮助信息将拷贝到它中。cchMax是上述缓冲区的尺寸。返回值是HRESULT常量,比如说S_OK或E_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 idCmd, UINT uFlags, UINT* pwReserved, LPSTR pszName, UINT 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 ( (LPWSTR) pszName, T2CW(szText), cchMax );

}

else

{

// ANSI版本

lstrcpynA ( pszName, T2CA(szText), cchMax );

}

return S_OK;

}

return E_INVALIDARG;

}

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

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

执行用户的选择

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

HRESULT IContextMenu::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo );

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

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

STDMETHODIMP CSimpleShlExt::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo )

{

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

if ( 0 != HIWORD( pCmdInfo->lpVerb ) )

return E_INVALIDARG;

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

switch ( LOWORD( pCmdInfo->lpVerb) )

{

case 0:

{

TCHAR szMsg [MAX_PATH + 32];

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

MessageBox ( pCmdInfo->hwnd, szMsg, _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”,右击,选中“资源包括”: 

在“资源包括”对话框的“编译时指令”中有一行类型库包括:

移除这行,VC弹出警告,点击“确定”:

移除类型库后,我们还需要修改两处代码,以告诉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

并设置其默认值为扩展COM的GUID,如“{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”,让其正常启动。

扩展的外观

增加扩展后的上下文菜单项

Explorer状态栏提示(fly-by help)

弹出的消息框,显示了所选中的文件名

版权与许可

作者:Michael Dunn

译者:Yesaidu

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

  1. (C#)Windows Shell 外壳编程系列6 - 执行

    (本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢-) 接上一节:(C#)Windows Shell 外壳编程系列5 - 获取图标 执行 许多人都知道 ShellExecute ,用于执行 ...

  2. (C#)Windows Shell 外壳编程系列4 - 上下文菜单(iContextMenu)(二)嵌入菜单和执行命令...

    (本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢-) 接上一节:(C#)Windows Shell 外壳编程系列3 - 上下文菜单(iContextMenu)(一)右键菜单 上一节说到如 ...

  3. (C#)Windows Shell 外壳编程系列总结

    http://www.cnblogs.com/lemony/category/88555.html 原文见上面链接中的1-9系列 1.基础,浏览一个文件夹 在win32中是以外壳名字空间的形式来组织文 ...

  4. (C#)Windows Shell 外壳编程系列1 - 基础,浏览一个文件夹

    (本系列文章由柠檬的(lc_mtt)原创,转载请注明出处,谢谢-) Windows Shell 编程,即 Windows 外壳编程.我们所看到的资源管理器以及整个桌面,都是一个 Shell. 关于 W ...

  5. Java基础之扩展GUI——高亮元素、上下文菜单、移动旋转元素、自定义颜色(Sketcher 10)...

    窗口应用程序. 本例在上一版的基础上实现了高亮元素.移动元素.上下文菜单.旋转元素.设置自定义颜色. 1.自定义常量包: 1 // Defines application wide constants ...

  6. windows编程之Windows Shell 编程

    这里仅仅是记录下该资源,推荐到下文列出的连接进行查看 用VC++ 进行Windows Shell 扩展编成 由ccc编译 序言: 看过一些对windows 外壳的扩展程序,在使用上一般都是直接利用wi ...

  7. 通过SharpShell快速实现Windows Shell扩展

    在.NET 4引入了CLR in-process side-by-side特性后,我们也可以通过C#编写Windows Shell了.我们可以在微软的All-In-One Code Framework ...

  8. Windows 右键菜单扩展

    右键菜单,指用户在对Windows Shell Object右键单击时,弹出的上下文菜单(Context Menu). 如上图所示的对右键菜单进行扩展,属于Windows Shell Extensio ...

  9. Windows Shell 扩展编程 第十五章

    转自:http://blog.csdn.net/chchzh/article/details/4597866 第十五章 SHELL扩展 谈到Windows Shell编程,Shell扩展是最重要的科目 ...

最新文章

  1. 摄像头ISP系统原理(上)
  2. 【译】Pure Proof-of-Stake Blockchains: Secure Blockchain Decentralization via Committees
  3. jquery.js把我的时间修改了为什么?_电气老手在PLC程序调试修改时的几个必备小窍门,看你知道几个?...
  4. jquery remove()不兼容问题解决方案
  5. 【OpenCV】视频输入与相似度测量
  6. Python typing —— 类型提示(type hint)
  7. 如何在 Mac 上自动启用隐藏式字幕?
  8. Excel如何按照单元格背景颜色排序
  9. 五星大饭店完整剧情,五星大饭店(完整集数)在线观看
  10. tensorflow-serving docker模型部署(以mnist为例)
  11. 新概念英语第三册51-60课(转)
  12. 深信服EDR终端检测平台-任意用户登录漏洞复现
  13. 上海居住证满3年或积分满120分房产税退税流程2014年
  14. 青云、UCloud、阿里云、腾讯云等分别都有哪些特点?
  15. HADOOP与HDFS数据压缩格式
  16. ROS实验笔记之——Intel Realsense l515激光相机的使用
  17. 如何用Visio画数据库实体关系图
  18. Html5 获取手机短信号码
  19. 搜索引擎排名优化与五个技巧详解
  20. 【仅供参考】资源共享汇总

热门文章

  1. 一文解决什么是关系模型
  2. 数据结构实验:一元稀疏多项式(计算器)
  3. [读书笔记]《APP研发录》之App图片缓存设计
  4. vue-element-admin登录接口修改
  5. 面试如何进行项目介绍
  6. 笔记本打开计算机盘非常慢,笔记本开机慢怎么办【方法步骤】
  7. R数据分析:交叉滞后模型基础与实例解析
  8. Hook机制之动态代理
  9. 普中温度+DS1302无法同时显示在LCD1602上
  10. 使用 Qt Quick 进行国际化和本地化