Shell扩展编程完全指南(1)一步步教你如何编写Shell扩展

Shell扩展编程完全指南

dedecms.com



linghuye 著

第一节 一步步教你如何编写Shell扩展

下载示例工程 - 11 Kb 本文来自织梦


所谓的Shell扩展就是能够添加某种功能到Windows Shell的COM对象。Windows里有着各种各样的扩展,但关于Shell扩展的原理以及如何编写Shell扩展的文档却很少。如果你想深入地了解Shell各方面的细节,我特别推荐Dino Esposito的著作《Visual C++ Windows Shell Programming》。但对于那些没有这本书的,或只对Shell扩展本身感兴趣的朋友,我写了这个编程指南希望能够帮助你理解怎样编写Shell扩展。该指南假设你理解COM和ATL的基本原理及应用。

第一节对Shell扩展进行了概括性的介绍, 并给出了一个上下文菜单的扩展以引起你对以后各章的兴趣.

但Shell扩展到底是什么玩意呢?
"Shell扩展"从字面上分两个部分,Shell与Extension。 Shell指Windows Explorer, 而Extension则指由你编写的当某一预先约定好的事件(如在以.doc为后缀的文件图标上单击右键)发生时由Explorer调用执行的代码。因此一个Shell扩展就是一个为Explorer添加功能的COM对象。

Shell扩展是个进程内服务器(运行在Explorer进程内),它实现了一些接口来处理与 Explorer 的通信。 ATL在我看来是设计Shell扩展最简单最快捷的方法, 如果没有它,你就不得不一遍又一遍地编写繁琐的 QueryInterface() 及AddRef()代码. 另外,在Windows NT 和 2000上调试Shell扩展相对比较容易一些,这我以后会讲到的。 织梦好,好织梦

Shell扩展有很多种类型,每种类型都在各自不同的事件发生时被调用运行,但也有一些扩展的类型和调用情形是非常相似的。
  本文来自织梦

 

dedecms.com

类型 织梦内容管理系统

何时被调用

本文来自织梦

应该作些什么 本文来自织梦

上下文菜单扩展处理器 内容来自dedecms

用户右键单击文件或文件夹对象时。

织梦好,好织梦

或在一个文件夹窗口中的背景处单击右键时(要求shell版本为4.71+)。 本文来自织梦

添加菜单项到上下文菜单中。

dedecms.com

属性页扩展处理器

织梦内容管理系统

要显示一个文件对象的属性框时。

dedecms.com

添加定制属性页到属性表中。

织梦好,好织梦

拖放目标扩展处理器 内容来自dedecms

用户用右键拖放文件对象到文件夹窗口或桌面时。 内容来自dedecms

添加菜单项到上下文菜单中。

dedecms.com

放置目标扩展处理器 内容来自dedecms

用户拖动Shell对象并将它放到一个文件对象上时。

内容来自dedecms

任何想要的操作。

织梦好,好织梦

提示信息扩展处理器(需要shell版本4.71+)

织梦内容管理系统

用户将鼠标盘旋于文件或其他Shell对象的图标上时。

dedecms.com

返回一个浏览器用于显示在提示框中的字符串。

内容来自dedecms




现在你可能想知道Shell扩展到底是什么样的. 如果你安装了 WinZip (有谁没装的吗?), 它就包含了多种的Shell扩展,其中也就有上下文菜单扩展. 下图是WinZip 8 为压缩文件对象添加的定制菜单项:



WinZip 编写了添加菜单项的代码, 提供了浏览器状态栏上的菜单项帮助提示, 并在用户选择一个菜单命令时执行相应的操作。

WinZip 还包括一个拖放目标扩展处理器. 该类型与上下文菜单十分类似, 但它是在用户用右键拖放文件时被触发的. 下图是 WinZip 定制的拖放菜单:



Shell扩展的类型很多,而且微软也正不断地在每一新版本的Windows中加入更多的扩展类型. 现在让我们把注意力放在上下文菜单上, 因为它们易于编写,效果也很明显(这能马上满足你). 本文来自织梦

在我们编写代码之前, 先说一下一些简化编码及调试工作的技巧. 当shell扩展被 Explorer调用后, 它会在内存中呆上一段时间, 这会使你无法重新编译并生成Shell扩展DLL文件. 要让 Explorer 更迅速地卸载Shell扩展执行文件,需要创建如下注册表项:

HKLM\Software\Microsoft\Windows\CurrentVersion\Explorer\AlwaysUnloadDLL

并将其值设为 "1". 对于Win9x, 这是你能做的最好的方法。 而在Win NT/2000上, 你可以找到如下键:

HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer

并创建一个名为DesktopProcess的DWORD值 1. 这会使桌面和任务栏运行在同一个进程中, 而其他每一个 Explorer 窗口都运行在它自己的每一个进程内. 也就是说,你可以在单个的Explorer 窗口内进行调试, 而后只要你关闭该窗口,你的DLL就会被马上卸载, 这就避免了因为DLL正被Windows使用而无法替换更新. 而如果不幸出现这种情况,你就不得不注销登录后再重新登录进Windows从而强制卸载使用中的Shell扩展DLL.

我将在稍后解释如何在Win9x中进行调试的细节.

开始编写上下文菜单 – 它该做些什么?

dedecms.com


开头先让我们做简单一些, 只弹出一个对话框以表明当前的扩展能够正常地工作. 我们把扩展关联到 .TXT 文件, 因此当用户右键单击文本文件对象时扩展就会被调用.

使用 AppWizard 开始
好吧, 让我们开始吧! 什么? 我还没告诉你怎样使用那些神秘的 shell 扩展接口? 别着急, 我会边进行边解释的。我觉得先解释一下一个概念再紧接着说明示例代码,对理解例子程序会更简单一些. 当然我也可以把所有的东西都先解释完,然后再解释代码, 但我觉得这样做不能吸引人的注意力。不管怎么样, 向 VC开火,开始!

运行AppWizard,生成一个名为SimpleExt的 ATL COM 工程. 保留所有默认的设置选项,点击”完成”. 现在我们已经有了一个空的 ATL工程,它可以编译并生成一个 DLL, 但我们还需要添加Shell扩展的 COM 对象. 在 ClassView 中, 右击 SimpleExt classes 条目, 选择 New ATL Object.

在ATL Object Wizard里, 第一页默认已经选择了 Simple Object , 所以单击 Next 即可. 在第二页中, 在Short Name 文本框里输入 SimpleShlExt ,点击 OK. (其余的文本框会自动填充完.) 这样就创建了一个名为 CSimpleShlExt 的类,其包含了实现COM对象最基本的代码. 我们将在这个类中加入我们自己的代码.
内容来自dedecms


初始化接口
当我们的shell扩展被加载时, Explorer 将调用我们所实现的COM对象的 QueryInterface() 函数以取得一个 IShellExtInit 接口指针. 该接口仅有一个方法 Initialize(), 其函数原型为:
 
copyright dedecms

HRESULT IShellExtInit::Initialize ( 
copyright dedecms
LPCITEMIDLIST pidlFolder, 

织梦内容管理系统

LPDATAOBJECT pDataObj, 
织梦内容管理系统
HKEY hProgID ); dedecms.com 


Explorer 使用该方法传递给我们各种各样的信息. PidlFolder是用户所选择操作的文件所在的文件夹的 PIDL 变量. (一个 PIDL [指向ID 列表的指针] 是一个数据结构,它唯一地标识了在Shell命名空间的任何对象, 一个Shell命名空间中的对象可以是也可以不是真实的文件系统中的对象.) pDataObj 是一个 IDataObject 接口指针,通过它我们可以获取用户所选择操作的文件名。 hProgID 是一个HKEY 注册表键变量,可以用它获取我们的DLL的注册数据. 在这个简单的扩展例子中, 我们将只使用到 pDataObj 参数.

要添加这个接口进 COM 对象, 先打开SimpleShlExt.h 文件, 然后加入下列标红的代码:
  copyright dedecms

#include <shlobj.h> 织梦好,好织梦 
#include <comdef.h> 
本文来自织梦
 本文来自织梦 
class ATL_NO_VTABLE CSimpleShlExt : copyright dedecms 
public CComObjectRootEx<CComSingleThreadModel>, 织梦好,好织梦 
public CComCoClass<CSimpleShlExt, &CLSID_SimpleShlExt>, 
内容来自dedecms
public IDispatchImpl<ISimpleShlExt, &IID_ISimpleShlExt, &LIBID_SIMPLEEXTLib>, dedecms.com 
public IShellExtInit 

dedecms.com

{ 

织梦内容管理系统

BEGIN_COM_MAP(CSimpleShlExt) 
dedecms.com
COM_INTERFACE_ENTRY(ISimpleShlExt) 内容来自dedecms 
COM_INTERFACE_ENTRY(IDispatch) 织梦好,好织梦 
COM_INTERFACE_ENTRY(IShellExtInit) 
dedecms.com
END_COM_MAP() 织梦好,好织梦 


COM_MAP是ATL 实现 QueryInterface()机制的宏. 它包含的列表告诉ATL 其它外部程序用QueryInterface()能从我们的COM对象获取哪些接口. 接着,在类声明里, 加入Initialize()的函数原型. 另外我们需要一个变量来保存文件名:
  织梦内容管理系统

protected: 

dedecms.com

TCHAR m_szFile [MAX_PATH]; 

本文来自织梦

 本文来自织梦 
public: 内容来自dedecms 
// IShellExtInit 织梦好,好织梦 
STDMETHOD(Initialize)(LPCITEMIDLIST, LPDATAOBJECT, HKEY); 
copyright dedecms


然后, 在 SimpleShlExt.cpp 文件中, 加入该函数方法的实现定义:
  dedecms.com

HRESULT CSimpleShlExt::Initialize ( 
dedecms.com
LPCITEMIDLIST pidlFolder, 
织梦内容管理系统
LPDATAOBJECT pDataObj, 

织梦好,好织梦

HKEY hProgID ) 织梦好,好织梦 


我们要做的是取得被右击选择的文件名,再把该文件名显示在弹出消息框中。可能会有多个文件同时被选择右击, 你可以用pDataObj 接口指针获取所有的文件名, 但现在为简单起见, 我们只获取第一个文件名.

文件名的存放格式与你拖放文件到带WS_EX_ACCEPTFILES风格的窗口时使用的文件名格式是一样的。 这就是说我们可以使用同样的API来获取文件名: DragQueryFile(). 首先我们先获取包含在IdataObject中的数据句柄:
  织梦内容管理系统

{ 
织梦内容管理系统
FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; 织梦内容管理系统 
STGMEDIUM stg = { TYMED_HGLOBAL }; 
copyright dedecms
HDROPhDrop; dedecms.com 
 copyright dedecms 
// 在数据对象内查找CF_HDROP 型数据. 织梦内容管理系统 
if ( FAILED( pDataObj->GetData ( &fmt, &stg ))) 

织梦好,好织梦

{ 

织梦好,好织梦

// Nope! Return an "invalid argument" error back to Explorer. 
织梦好,好织梦
return E_INVALIDARG; 

织梦内容管理系统

} 
copyright dedecms
 织梦好,好织梦 
// 获得指向实际数据的指针 
copyright dedecms
hDrop = (HDROP) GlobalLock ( stg.hGlobal ); 织梦好,好织梦 
 copyright dedecms 
// 检查非NULL. copyright dedecms 
if ( NULL == hDrop ) 

dedecms.com

{ 内容来自dedecms 
return E_INVALIDARG; 织梦内容管理系统 
} copyright dedecms 


请注意错误检查,特别是指针的检查。 由于我们的扩展运行在 Explorer 进程内, 要是我们的代码崩溃了, Explorer也会随之崩溃. 在Win 9x上, 这样的一个崩溃可能导致需要重启系统.

所以, 现在我们有了一个 HDROP 句柄, 我们就可以获取我们需要的文件名了:
  织梦好,好织梦

// 有效性检查– 保证最少有一个文件名. 织梦好,好织梦 
UINT uNumFiles = DragQueryFile ( hDrop, 0xFFFFFFFF, NULL, 0 ); 

织梦内容管理系统

 本文来自织梦 
if ( 0 == uNumFiles ) 本文来自织梦 
{ 

内容来自dedecms

GlobalUnlock ( stg.hGlobal ); 本文来自织梦 
ReleaseStgMedium ( &stg ); 

dedecms.com

return E_INVALIDARG; copyright dedecms 
} 

copyright dedecms

 
织梦好,好织梦
HRESULT hr = S_OK; 

内容来自dedecms

 
织梦好,好织梦
// 取得第一个文件名并把它保存在类成员m_szFile 中. 
内容来自dedecms
if ( 0 == DragQueryFile ( hDrop, 0, m_szFile, MAX_PATH )) 
本文来自织梦
{ dedecms.com 
hr = E_INVALIDARG; 内容来自dedecms 
} 

copyright dedecms

 

dedecms.com

GlobalUnlock ( stg.hGlobal ); 本文来自织梦 
ReleaseStgMedium ( &stg ); 织梦好,好织梦 
 copyright dedecms 
return hr; 

织梦内容管理系统

} 
copyright dedecms


要是我们返回 E_INVALIDARG, Explorer 将不会继续调用以后的扩展代码. 要是返回 S_OK, Explorer 将再一次调用QueryInterface() 获取另一个我们下面就要添加的接口指针: IContextMenu.

与上下文菜单交互的接口
一旦 Explorer 初始化了扩展, 它就会接着调用 IContextMenu 的方法让我们添加菜单项, 提供状态栏上的提示, 并响应执行用户的选择.

添加IContextMenu 接口到Shell扩展类似于上面IshellExtInit接口的添加 .打开 SimpleShlExt.h,添加下列标红的代码:
  织梦好,好织梦

class ATL_NO_VTABLE CSimpleShlExt : 

copyright dedecms

public CComObjectRootEx<CComSingleThreadModel>, copyright dedecms 
public CComCoClass<CSimpleShlExt, &CLSID_SimpleShlExt>, 织梦好,好织梦 
public IDispatchImpl<ISimpleShlExt, &IID_ISimpleShlExt, &LIBID_SIMPLEEXTLib>, 本文来自织梦 
public IShellExtInit, 织梦好,好织梦 
public IContextMenu dedecms.com 
{ 

本文来自织梦

BEGIN_COM_MAP(CSimpleShlExt) 

织梦好,好织梦

COM_INTERFACE_ENTRY(ISimpleShlExt) 本文来自织梦 
COM_INTERFACE_ENTRY(IDispatch) dedecms.com 
COM_INTERFACE_ENTRY(IShellExtInit) 织梦内容管理系统 
COM_INTERFACE_ENTRY(IContextMenu) copyright dedecms 
END_COM_MAP() copyright dedecms 


添加 IContextMenu 方法的函数原型:
 

dedecms.com

public: 本文来自织梦 
// IContextMenu 织梦内容管理系统 
STDMETHOD(GetCommandString)(UINT, UINT, UINT*, LPSTR, UINT); 

内容来自dedecms

STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO); 内容来自dedecms 
STDMETHOD(QueryContextMenu)(HMENU, UINT, UINT, UINT, UINT); 
内容来自dedecms


修改上下文菜单
IContextMenu 有三个方法. 第一个是 QueryContextMenu(), 它让我们可以修改上下文菜单. 其原型为:
 

本文来自织梦

HRESULT IContextMenu::QueryContextMenu ( copyright dedecms 
HMENU hmenu, 本文来自织梦 
UINTuMenuIndex, copyright dedecms 
UINTuidFirstCmd, 
dedecms.com
UINTuidLastCmd, 本文来自织梦 
UINTuFlags ); 织梦好,好织梦 


hmenu 上下文菜单句柄. uMenuIndex 是我们应该添加菜单项的起始位置. uidFirstCmd 和 uidLastCmd 是我们可以使用的菜单命令ID值的范围. uFlags 标识了Explorer 调用QueryContextMenu()的原因, 这我以后会说到的.

而返回值根据你所查阅的文档的不同而不同. Dino Esposito 的书中说返回值是你所添加的菜单项的个数. 而 VC6.0所带的MSDN 又说它是我们添加的最后一个菜单项的命令ID加上 1. 而最新的 MSDN 又说:

将返回值设为你为各菜单项分配的命令ID的最大差值,加上1. 例如, 假设 idCmdFirst 设为5,而你添加了三个菜单项 ,命令ID分别为 5, 7, 和 8. 这时返回值就应该是:

MAKE_HRESULT(SEVERITY_SUCCESS, 0, 8 - 5 + 1).

我是一直按 Dino 的解释来做的, 而且工作得很好.实际上, 他的方法与最新的 MSDN 是一致的, 只要你严格地使用 uidFirstCmd作为第一个菜单项的ID,再对接续的菜单项ID每次加1.

我们暂时的扩展仅加入一个菜单项,所以 QueryContextMenu() 非常简单:
  织梦内容管理系统

HRESULT CSimpleShlExt::QueryContextMenu ( 
dedecms.com
HMENU hmenu, 
copyright dedecms
UINTuMenuIndex, 织梦内容管理系统 
UINTuidFirstCmd, 织梦好,好织梦 
UINTuidLastCmd, 
copyright dedecms
UINTuFlags ) copyright dedecms 
{ 

dedecms.com

// 如果标志包含CMF_DEFAULTONLY 我们不作任何事情. 

内容来自dedecms

if ( uFlags & CMF_DEFAULTONLY ) 内容来自dedecms 
{ 
本文来自织梦
return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 0 ); 内容来自dedecms 
} 本文来自织梦 
 
内容来自dedecms
InsertMenu ( hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd, _T("SimpleShlExt Test Item") ); 
dedecms.com
 

dedecms.com

return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 1 ); 织梦好,好织梦 
} 
内容来自dedecms


首先我们检查 uFlags. 你可以在 MSDN中找到所有标志的解释, 但对于上下文菜单扩展而言, 只有一个值是重要的: CMF_DEFAULTONLY. 该标志告诉Shell命名空间扩展保留默认的菜单项,这时我们的Shell扩展就不应该加入任何定制的菜单项,这也是为什么此时我们要返回 0的原因. 如果该标志没有被设置, 我们就可以修改菜单了 (使用 hmenu 句柄), 并返回1 告诉Shell我们添加了一个菜单项.

在状态栏上显示提示帮助
下一个要被调用的IContextMenu 方法是 GetCommandString(). 如果用户是在浏览器窗口中右击文本文件,或选中一个文本文件后单击文件菜单时,状态栏会显示提示帮助.我们的 GetCommandString() 函数将返回一个帮助字符串供浏览器显示.

GetCommandString() 的原型是:
 

dedecms.com

HRESULT IContextMenu::GetCommandString ( 

copyright dedecms

UINT idCmd, 
本文来自织梦
UINT uFlags, 

织梦好,好织梦

UINT *pwReserved, 本文来自织梦 
LPSTR pszName, 

本文来自织梦

UINT cchMax ); 织梦好,好织梦 


idCmd 是一个以0为基数的计数器,标识了哪个菜单项被选择. 因为我们只有一个菜单项, 所以idCmd 总是0. 但如果我们添加了3个菜单项, idCmd 可能是 0, 1, 或 2. uFlags 是另一组标志(我以后会讨论到的). PwReserved 可以被忽略. pszName 指向一个由Shell拥有的缓冲区,我们将把帮助字符串拷贝进该缓冲区. cchMax 是该缓冲区的大小. 返回值是S_OK 或 E_FAIL.

GetCommandString() 也可以被调用以获取菜单项的动作( "verb") . verb 是个语言无关性字符串,它标识一个可以加于文件对象的操作。ShellExecute()的文档中有详细的解释, 而有关verb的内容足以再写一篇文章, 简单的解释是:verb 可以直接列在注册表中(如 "open" 和 "print"等字符串), 也可以由上下文菜单扩展创建. 这样就可以通过调用ShellExecute()执行实现在Shell扩展中的代码.

不管怎样, 我说了这多只是为了解释清楚GetCommandString() 的作用. 如果 Explorer 要求一个帮助字符串,我们就提供给它. 如果 Explorer 要求一个verb, 我们就忽略它. 这就是 uFlags 参数的作用. 如果 uFlags 设置了GCS_HELPTEXT 位, 则 Explorer 是在要求帮助字符串. 而且如果 GCS_UNICODE 被设置, 我们就必须返回一个Unicode字符串. 织梦内容管理系统

我们的 GetCommandString() 如下:
 

织梦好,好织梦

#include <atlconv.h>// 为使用ATL 字符串转换宏而包含的头文件 织梦好,好织梦 
 
织梦好,好织梦
HRESULT CSimpleShlExt::GetCommandString ( 本文来自织梦 
UINTidCmd, copyright dedecms 
UINTuFlags, 

本文来自织梦

UINT* pwReserved, 本文来自织梦 
LPSTR pszName, 
织梦内容管理系统
UINTcchMax ) 
织梦内容管理系统
{ 
织梦好,好织梦
USES_CONVERSION; 
本文来自织梦
 

copyright dedecms

//检查idCmd, 它必须是0,因为我们仅有一个添加的菜单项. 内容来自dedecms 
if ( 0 != idCmd ) 

织梦好,好织梦

return E_INVALIDARG; 

本文来自织梦

 
dedecms.com
// 如果Explorer 要求帮助字符串,就将它拷贝到提供的缓冲区中.  织梦好,好织梦 
 
copyright dedecms
if ( uFlags & GCS_HELPTEXT ) copyright dedecms 
{ dedecms.com 
LPCTSTR szText = _T("This is the simple shell extension's help"); 
copyright dedecms
 本文来自织梦 
if ( uFlags & GCS_UNICODE ) copyright dedecms 
{ 织梦内容管理系统 
// 我们需要将pszName 转化为一个Unicode 字符串, 接着使用Unicode字符串拷贝API. 织梦内容管理系统 
lstrcpynW ( (LPWSTR) pszName, T2CW(szText), cchMax ); 

织梦好,好织梦

} 织梦内容管理系统 
else 

dedecms.com

{ 内容来自dedecms 
// 使用ANSI 字符串拷贝API 来返回帮助字符串. 
copyright dedecms
lstrcpynA ( pszName, T2CA(szText), cchMax ); 织梦内容管理系统 
} dedecms.com 
 
copyright dedecms
return S_OK; 
本文来自织梦
} 本文来自织梦 
 织梦内容管理系统 
return E_INVALIDARG; 织梦内容管理系统 
} 织梦内容管理系统 


这里没有什么特别的代码; 我用了硬编码的字符串并把它转换为相应的字符集. 如果你从未使用过ATL字符串转化宏,你一定要学一下,因为当你传递Unicode字符串到COM和OLE函数时,使用转化宏会很有帮助的. 我在上面的代码中使用了T2CW 和 T2CA 将TCHAR 字符串分别转化为Unicode 和 ANSI字符串. 函数开头处的USES_CONVERSION 宏其实声明了一个将被转化宏使用的局部变量.

要注意的一个问题是: lstrcpyn() 保证了目标字符串将以null为结束符. 这与C运行时(CRT)函数 strncpy()不同. 当要拷贝的源字符串的长度大于或等于cchMax 时 strncpy()不会添加一个null结束符. 我建议总使用lstrcpyn(), 这样你就不必在每一个strncpy()后加入检查保证字符串以null为结束符的代码.

执行用户的选择
IContextMenu 接口的最后一个方法是 InvokeCommand(). 当用户点击我们添加的菜单项时该方法将被调用. 其函数原型是:

HRESULT IContextMenu::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo );

织梦内容管理系统


CMINVOKECOMMANDINFO 结构带有大量的信息, 但我们只关心 lpVerb 和 hwnd 这两个成员. lpVerb参数有两个作用 – 它或是可被激发的verb(动作)名, 或是被点击的菜单项的索引值. hwnd 是用户激活我们的菜单扩展时所在的浏览器窗口的句柄.

因为我们只有一个扩展的菜单项, 我们只要检查lpVerb 参数, 如果其值为0, 我们可以认定我们的菜单项被点击了. 我能想到的最简单的代码就是弹出一个信息框, 这里的代码也就做了这么多. 信息框显示所选的文件的文件名以证实代码正确地工作.
 

织梦好,好织梦

HRESULT CSimpleShlExt::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo ) 内容来自dedecms 
{ 
dedecms.com
// 如果lpVerb 实际指向一个字符串, 忽略此次调用并退出. 织梦内容管理系统 
if ( 0 != HIWORD( pCmdInfo->lpVerb )) 织梦内容管理系统 
return E_INVALIDARG; 织梦好,好织梦 
 
织梦好,好织梦
// 点击的命令索引– 在这里,唯一合法的索引为0. 
织梦好,好织梦
switch ( LOWORD( pCmdInfo->lpVerb )) 织梦好,好织梦 
{ 本文来自织梦 
case 0: 
本文来自织梦
{ 
copyright dedecms
TCHAR szMsg [MAX_PATH + 32]; 

dedecms.com

 织梦好,好织梦 
wsprintf ( szMsg, _T("The selected file was:\n\n%s"), m_szFile ); 
织梦好,好织梦
 

内容来自dedecms

MessageBox ( pCmdInfo->hwnd, szMsg, _T("SimpleShlExt"), dedecms.com 
MB_ICONINFORMATION ); 织梦内容管理系统 
 

本文来自织梦

return S_OK; 织梦内容管理系统 
} copyright dedecms 
break; 
织梦内容管理系统
 织梦内容管理系统 
default: 本文来自织梦 
return E_INVALIDARG; 

本文来自织梦

break; 

本文来自织梦

} copyright dedecms 
} copyright dedecms 


注册Shell扩展
现在我们已经实现了所有需要的COM接口. 可是我们怎样才能让浏览器使用我们的扩展呢? ATL 自动生成注册COM DLL服务器的代码, 但这只是让其它程序可以使用我们的DLL. 为了告诉浏览器使用我们的扩展, 我们需要在文本文件类型的注册表键下注册扩展:

HKEY_CLASSES_ROOT\txtfile

在这个键下, 有个名为 ShellEx 的键保存了一个与文本文件关联的Shell扩展的列表. 在 ShellEx 键下, ContextMenuHandlers 键保存了上下文菜单扩展的列表. 每个扩展都在ContextMenuHandlers下创建了一个子键并将其默认值设为扩展COM的GUID. 所以, 对于我们这个简单的扩展, 我们将创建下键:

HKEY_CLASSES_ROOT\txtfile\ShellEx\ContextMenuHandlers\SimpleShlExt

并将其默认值设为我们的 GUID: "{5E2121EE-0300-11D4-8D3B-444553540000}".

你不必写任何代码就可以完成注册操作. 如果你看一下Fileview页的文件列表, 你会看到SimpleShlExt.rgs. 该文本文件将被ATL分析, 并指导ATL在该COM服务器注册时添加附加的注册键, 而注销时又该删除哪些键. 以下是所指定添加的注册表项:
 

织梦内容管理系统

HKCR 内容来自dedecms 
{ 本文来自织梦 
NoRemove txtfile 织梦内容管理系统 
{ 

dedecms.com

NoRemove ShellEx 
织梦内容管理系统
{ 织梦内容管理系统 
NoRemove ContextMenuHandlers 

内容来自dedecms

{ 本文来自织梦 
ForceRemove SimpleShlExt = s '{5E2121EE-0300-11D4-8D3B-444553540000}' 本文来自织梦 
} 织梦好,好织梦 
} 
copyright dedecms
} 本文来自织梦 
} 
copyright dedecms


每一行代表一个注册表键, "HKCR"是 HKEY_CLASSES_ROOT 的缩写. NoRemove 关键字表示当该COM服务器注销时该键 不用被删除. 最后一行有些复杂. ForceRemove 关键字表示如果该键已存在, 那么在新键添加之前该键先应被删除. 这行脚本的余下部分指定一个字符串,它将被存为 SimpleShlExt 键的默认值.

在这我插几句话. 我们是在 HKCR\txtfile下注册的. 但是 "txtfile" 名并不是一个永久的或预定好的名称. 如果你看一下 HKCR\.txt, 该键的默认值正是txtfile. 这样就会有一些副作用:

我们不能可靠地使用 RGS 教本,因为 "txtfile" 可能不是正确的键名.
一些文本编辑软件可能安装到系统并直接关联到 .TXT 文件. 如果它改变了HKCR\.txt 键的默认值, 所有现存的Shell扩展都将停止工作.
在我看来,这确是个设计上的错误. 我认为微软也是这么想的, 因为最新的扩展类型, 如QueryInfo扩展注册在 .txt 键下.

好了,到此为止. 最后还有一个注册细节. 在NT/2000上, 我们还得将我们的扩展放到 "approved" 扩展列表中. 如果我们不这样做, 我们的扩展不会被没有管理员权限的用户调用. 该列表保存在: 内容来自dedecms

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

在该键下, 我们要创建一个以我们的GUID 为名的字符串键,键的内容任意. 与之有关的代码在DllRegisterServer() 和 DllUnregisterServer() 函数中. 只是些简单的注册表获取, 我也就不在这写出了. 你可以在例子工程代码中找到它.

调试Shell 扩展
最终你会写一个不会这么简单的扩展, 那时你就不得不进行调试. 打开你的工程设置, 在 Debug 页” Executable for debug session”编辑框中输入浏览器程序的全路径, 如:"C:\windows\explorer.exe". 如果你使用的是 NT 或 2000, 并且你已经设置了上述的 DesktopProcess 注册键, 那么当你按F5进行调试时就会打开一个新的浏览器窗口. 只要你在这个窗口内完成你所有的工作,当你关闭该窗口时扩展同时会被卸出内存,这样就不会防碍我们重建 DLL了.

在Windows 9x上, 在重新调试之前你不得不关闭Shell. 你可以: 点击 “开始”, 再点击”关闭”. 按住 Ctrl+Alt+Shift 并点击”取消”. 这样就会关闭Shell, 你会看到桌面消失了. 接着,你可以切换到 MSVC 再按 F5进行调试. 要中止调试, 按 Shift+F5 关闭. 完成调试后, 你可以从”开始 运行” Explorer.exe以正常重起.

内容来自dedecms



扩展的样子
下面就是我们添加自定义菜单项后的样子:



看,我们的菜单项在那!

下图是浏览器状态栏的帮助提示字符串的显示:



弹出信息框如下图, 显示了所选的文件名:



待续...
在下一节第二节 中, 我将创建一个新的菜单扩展,并演示如何一次操作多个被选文件。

本文来自织梦