Windows Shell扩展编程傻瓜手册大全.docx
- 文档编号:23340900
- 上传时间:2023-05-16
- 格式:DOCX
- 页数:26
- 大小:464.92KB
Windows Shell扩展编程傻瓜手册大全.docx
《Windows Shell扩展编程傻瓜手册大全.docx》由会员分享,可在线阅读,更多相关《Windows Shell扩展编程傻瓜手册大全.docx(26页珍藏版)》请在冰豆网上搜索。
WindowsShell扩展编程傻瓜手册大全
WindowsShell扩展编程傻瓜手册大全:
上下文菜单扩展
PartI:
Astep-by-steptutorialonwritingshellextensions
第一节:
Windowsshell扩展初步:
上下文菜单扩展
作者:
MichaelDunn
译者:
yesaidu
源代码下载:
12
目录
●README
●系列绪言
●第一部分绪言
●从AppWizard开始
●初始化接口
●上下文菜单交互接口
○更改上下文菜单
○在状态栏显示拉线式(fly-by)帮助
○执行用户选择
○其他代码细节
●注册Shell扩展
●调试Shell扩展
●所有的外观
●版权与许可
●修订历史
README
我想,你在行动之前,或者你在本手册的讨论板发帖之前应该阅读这份材料。
本手册最初是用VC6编写的。
现在,VC8都出来了,我感觉是时候对本手册进行升级到VC7.1了。
(通过VC7.1自动升级VC6项目,并不一定会完全地完成代码转换;因此,VC7.1用户可能碰到这样的现象,即在转换、编译示例代码后,运行时可能没有效果或出错。
)只要我仔细检查并更新本手册,本手册将体现VC7.1的新特点。
我将会提供VC7.1项目的源码下载。
VC2005用户要注意了:
VC2005体验版(Expressedition)没有一同发布ATL或MFC。
既然本手册用到了ATL,有时还使用了MFC,因此,你不能用VC2005体验版来编译示例代码。
如果你正使用VC6,那么,你应该设法取得最新的平台SDK。
你可以使用WEB安装版(webinstallversion),或者下载CAB文件或者ISO镜像包,安装它们到本地。
确认把SDK的INCLUDE和LIB目录添加到了VC的搜索路径中。
你能在PSDK程序组中找到VisualStudioRegistration目录。
这是一个好主意,无论你使用VC7,还是用VC8,你都能取得最新的PSDK头文件和库文件。
VC7用户注意了:
如果你没有更新PSDK,必须改变默认的INCLUDE路径。
确信“VC++目录”-“包含文件”列表的第一项是$(VCInstallDir)PlatformSDK\include,它在($VCInstallDir)include前面,如下图:
由于一直没有使用过VC8,因此我不确定示例代码在VC8上是否可以通过编译。
只是希望,把VC7项目升级到VC8的自动转换功能比从VC6到VC7的要好些。
如果你使用VC8编译示例时遇到了任何疑惑,请在讨论板发帖。
手册绪言
所谓shell扩展就是能增加某些功能到Windows资源管理器的COM对象。
Shell扩展有很多内容,但关于它们的文档资料却非常少见。
(自从我最先发表这份手册的六年来,我相信情况要好多了。
)如果你想深入Windowsshell的内部,极力推荐DinoEsposito的巨作VisualC++WindowsShellProgramming(ISBN1861001843)。
对于没有这本书的人,或者仅仅对shell扩展感兴趣的朋友,我将给你一个惊喜:
一本有关shell扩展编程的傻瓜手册。
即使本手册并未让你感到惊喜,那么,对你理解如何编写shell扩展也会提供很好的帮助。
本手册假定你理解并掌握了COM和ATL的基本原理和应用。
如果你还需要学习COM基本原理,请参考IntrotoCOM。
第一节介绍了shell扩展的概要,并提供了一个上下文菜单扩展的示例,使你对后面的章节充满兴趣。
从字面上看,shell扩展包括两个方面:
shell和扩展。
所谓shell,就是资源管理器Explorer;而扩展就是指在预定的事件发生时由Explorer调用执行的代码(比如,在.DOC文件上右击)。
因此,shell扩展就是为Explorer增添功能的COM对象。
shell扩展是一个进程内服务器,它实现了跟Explorer通信的接口。
ATL是设计一个shell扩展,并使之运行的最简单办法;这样你就不用为一遍又一遍的编写QueryInterface()和AddRef()而大伤脑筋。
在WindowsNT下调试shell扩展要更容易些,这点,我在后面还会谈到。
Shell扩展有很多种类型,每一类型都有其被调用的时机:
即每种类型在不同的事件发生时被调用执行。
下表列出了一些较常见的类型,以及它们被调用的情况:
类型
被调用的时机
它可以做什么
Contextmenu扩展处理器
用户在文件对象或文件夹对象或目录窗口背景(需要shellv4.71+以上)单击右键
在上下文菜单中添加菜单项
Propertysheet扩展处理器
文件属性对话框显示时
在属性对话框中定制属性页
Draganddrop扩展处理器
用户用右键拖放文件到文件夹窗口或桌面时
在上下文菜单中添加菜单项
Drophandler扩展处理器
用户拖对象并将其放到文件上时
任何你想做的
QueryInfo扩展处理器
(需要shellversion4.71+)
用户在文件、“我的电脑”等其他shell对象的图标上悬停时
返回一个Explorer显示在工具提示中的字符串
第一节绪言
现在,你可能有很多的疑问:
为什么扩展看起来像Explorer?
它到底是什么样的?
一个例子就是WinZip(或者WinRAR,我没安装WinZip^_^――译者)——它包含了多种shell扩展,其中之一就是上下文扩展。
下图是WinZip(其实是WinRA的^_^――译者)为压缩文件在上下文菜单中添加的菜单项:
WinZip编写了增加菜单项的代码,提供了Explorer状态栏上的菜单项帮助提示(fly-byhelp),并在用户选择一个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”的ATLCOM工程:
去掉属性化,保留其它默认选项,点击“完成”。
现在,我们有了一个空的ATL项目,它可以编译生成一个DLL,但我们还需要添加shell扩展COM对象。
在类视图中,右击“SimpleExt”项,选择“新建ATL对象”(VC7,选择“添加”→“添加类”,下图。
本文的环境是WindowsXP+VC7.1,因此附图都是VC7的)。
在ATL对象向导中,第一页已经选择了“简单对象”,点击“下一步”。
在第二页,在“简称”编辑框中输入“SimpleShlExt”(其他编辑框会自动完成):
在默认情况下,向导将创建以C和脚本客户端为基础的OLE自动化兼容的COM对象。
我们的扩展仅仅由Explorer调用,因此我们去掉自动化支持。
在“属性”页,选择“接口”类型为“自定义”,并且选择“聚合”为“否”:
点击“完成”,就创建了CSimpleShlExt类,它包含了实现COM对象的最基本代码。
我们将向这个类加入代码。
初始化接口
当我们的shell扩展被加载时,Explorer将调用QueryInterface()函数,以取得IShellExtInit接口指针。
该接口仅有一个方法Initialize(),其函数原型如下:
HRESULTIShellExtInit:
:
Initialize(
LPCITEMIDLISTpidlFolder,
LPDATAOBJECTpDataObj,
HKEYhProgID)
Explorer通过该方法向我们传递各种各样的信息。
pidlFolder是用户所操作文件所在的文件夹的PIDL(PIDL[pointertoanIDlist],指向ID列表的指针,是一个数据结构,它唯一标识了在shell空间的任何对象,这个对象是或者不是文件系统的对象。
)pDataObj是一个IDataObj接口变量,通过它可以取得用户正操作的文件名。
hProgID是一个HKEY接口变量,通过它可以取得扩展DLL的注册信息。
在本例中,仅仅需要pDataObj参数。
要添加这一方法到我们的COM对象,打开文件SimpleShlExt,加入下列粗体的行。
AppWizard生成了一些不必需的COM关系代码,既然我们不实现我们自己的接口,所以我指出这些失败的能被移除的代码(带删除线的那些):
#include
#include
classATL_NO_VTABLECSimpleShlExt:
publicCComObjectRootEx
publicCComCoClass
publicISimpleShlExt,
publicIShellExtInit
{
BEGIN_COM_MAP(CSimpleShlExt)
COM_INTERFACE_ENTRY(ISimpleShlExt)
COM_INTERFACE_ENTRY(IShellExtInit)
END_COM_MAP()
COM_MAP是ATL实现QueryInterface的宏,它告诉ATL其它程序能从COM对象取得哪些接口。
在类声明中,加入Initialize函数。
此外,还需要一个变量来保存文件名:
protected:
TCHARm_szFile[MAX_PATH];
public:
//IShellExtInit
STDMETHODIMPInitialize(LPCITEMIDLIST,LPDATAOBJECT,HKEY);
接着,在文件SimpleShlExt.cpp中,添加Initialize的实现代码:
STDMETHODIMPCSimpleShlExt:
:
Initialize(
LPCITEMIDLISTpidlFolder,
LPDATAOBJECTpDataObj,
HKEYhProgID)
我们要做的是取得右键单击选中的文件名,并把它显示在消息框中。
如果选中了多个文件,可以通过pDataObj接口指针来访问它们,不过为了保持例子的简单,我们只获取第一个文件名。
文件名的格式和拖曳文件到WS_EX_ACCEPTFILES风格的窗口是的文件名格式一致,这样说来,我们可以通过同样的API:
DragQueryFile来取得文件名。
我们先取得包含在IDataObject中的数据句柄:
voidCSimpleShlExt:
:
Initialize(LPCITEMIDLISTpidlFolder,LPDATAOBJECTpDataObj,HKEYhProgID)
{
FORMATETCfmt={CF_HDROP,NULL,DVASPECT_CONTENT,-1,TYMED_HGLOBAL};
STGMEDIUMstg={TYMED_HGLOBAL};
HDROPhDrop;
//在数据对象内查找CF_HDROP类型数据。
//如果没有数据,返回一个错误(“无效参数”)给Explorer。
if(FAILED(pDataObj->GetData(&fmt,&stg)))
returnE_INVALIDARG;
//取得指向实际数据的指针。
hDrop=(HDROP)GlobalLock(stg.hGlobal);
//确保非NULL
if(NULL==hDrop)
returnE_INVALIDARG;
注意,错误检查是极其重要的,尤其是对指针的检查。
因为我们的扩展运行于Explorer进程空间,如果我们的程序挂了,Explorer会跟着挂。
在Win9x系统上,这样的崩溃可能导致需要重启系统。
现在,我们有了HDROP句柄,可以取得所需的文件名了:
//有效性检查,至少有一个文件名
UINTuNumFiles=DragQueryFile(hDrop,0xFFFFFFFF,NULL,0);
HRESULThr=S_OK;
if(0==uNumFiles)
{
GlobalUnlock(stg.hGlobal);
ReleaseStgMedium(&stg);
returnE_INVALIDARG;
}
//取得第一个文件名,保存到m_szFile
if(0==DragQueryFile(hDrop,0,m_szFile,MAX_PATH))
hr=E_INVALIDARG;
GlobalUnlock(stg.hGlobal);
ReleaseStgMedium(&stg);
returnhr;
}
如果返回E_INVALIDARG,Explorer不会在右键事件时再调用我们的扩展。
如果返回S_OK,Explorer将再次调用QueryInterface(),以取得另一接口:
IContextMenu。
与上下文菜单交互的接口
一旦Explorer初始化了我们的扩展,它将调用IContextMenu方法来增加菜单项、提供状态栏帮助(fly-byhelp),以及响应用户的选择。
添加IContextMenu接口与IShellExtInit相类似。
打开文件SimpleShlExt.h,加入下列加粗的行:
classATL_NO_VTABLECSimpleShlExt:
publicCComObjectRootEx
publicCComCoClass
publicIShellExtInit,
publicIContextMenu
{
BEGIN_COM_MAP(CSimpleShlExt)
COM_INTERFACE_ENTRY(IShellExtInit)
COM_INTERFACE_ENTRY(IContextMenu)
END_COM_MAP()
接着,添加IContextMenu方法的函数原型:
public:
//IContextMenu
STDMETHODIMPGetCommandString(UINT,UINT,UINT*,LPSTR,UINT);
STDMETHODIMPInvokeCommand(LPCMINVOKECOMMANDINFO);
STDMETHODIMPQueryContextMenu(HMENU,UINT,UINT,UINT,UINT);
修改上下文菜单
IContextMenu有三个方法。
第一个QueryContextMenu()修改上下文菜单。
它的原型如下:
HRESULTIContextMenu:
:
QueryContextMenu(
HMENUhmenu,
UINTuMenuIndex,
UINTuidFirstCmd,
UINTuidLastCmd,
UINTuFlags);
hmenu是上下文菜单句柄。
uMenuIndex是我们要添加菜单项的开始位置。
uidFirstCmd和uidLastCmd是菜单命令ID值范围。
uFlags表明Explorer调用QueryContextMenu()的缘由,这个后面还会谈到。
关于此方法的返回值,你翻阅不同的文档,可能得到不同的答案。
DinoEsposito在他的书中认为这个返回值是所添加的菜单项的数目。
但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()函数非常简单:
STDMETHODIMPCSimpleShlExt:
:
QueryContextMenu(
HMENUhmenu,UINTuMenuIndex,UINTuidFirstCmd,
UINTuidLastCmd,UINTuFlags)
{
//如果标识包含了CMF_DEFAULTONLY,那么,我们啥都不做
if(uFlags&CMF_DEFAULTONLY)
returnMAKE_HRESULT(SEVERITY_SUCCESS,FACILITY_NULL,0);
InsertMenu(hmenu,uMenuIndex,MF_BYPOSITION,uidFirstCmd,_T("简单SHELL扩展测试"));
returnMAKE_HRESULT(SEVERITY_SUCCESS,FACILITY_NULL,1);
}
首先,我们检查uFlags的值。
在MSDN内,你能找到所有的标识和它们的解释,但对于上下文菜单扩展而言,仅仅一个值是有意思的:
即CMF_DEFAULTONLY。
该标识告诉shell命名空间扩展保留默认的菜单项;(如果设置了它的话,)shell扩展将不增加任何的菜单项。
这也是我们为什么返回0的原因。
如果未设置它,我们就可以通过句柄hmenu来修改菜单,并返回1告诉shell增加了一个菜单项。
在状态栏显示提示帮助(fly-byhelp)
下一个要被调用的IContextMenu方法是GetCommandString()。
当用户在Explorer窗口中右击文本文件,或者选中文本文件后点击“文件”菜单,鼠标指到我们添加的菜单项时,状态栏将显示提示信息。
GetCommandString()函数返回一个字符串供Explorer显示。
GetCommandString()原型如下:
HRESULTIContextMenu:
:
GetCommandString(
UINTidCmd,UINTuFlags,UINT*pwReserved,
LPSTRpszName,UINTcchMax);
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
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Windows Shell扩展编程傻瓜手册大全 Shell 扩展 编程 傻瓜 手册 大全