Shell可以实现扩展,Shell扩展程序可以实现以下功能。
◇为特定文件类型、所有文件类型、网络邻居、回收站、驱动器、网络共享文件夹、打印机、光盘驱动器、目录、文件夹背景(窗口的空白处)等Shell对象的右键菜单(称作上下文件菜单,Context Menu)增加菜单项。
◇当选中上下文件菜单的菜单项时进行相关处理。
◇定制将文件、文件夹拖拽至其他目录中时的操作。
◇定制右键拖曳Shell对象图标的菜单及操作。
◇根据文件内容定制图标。
◇定制属性对话框及分页项。
◇定制提示对话框(当鼠标指针停留在图标时弹出的提示信息)。
◇当使用“详细信息”显示图标时定制列。
◇加速或禁止目录、特定文件的移动、复制、删除、重命名等操作。
◇在开始菜单或Explorer中增加定制的搜索引擎。
从这些功能还可以扩展出很多特殊的应用,比如:
◇监视文件或目录的移动。
◇监视回收站动作,如还原项目、清空等。
一、基本概念
Shell扩展处理例程的代码实现位于DLL文件中,是一个符合COM接口的对象。在完成DLL的设计后需要在注册表进行注册,Shell扩展才能正常工作。
1、Handler(处理例程)
处理例程(Handler)是一系列函数及接口,当Shell事件发生时,会调用处理例程进行处理。
比如当在图标上点击右键时,会调用上下文件菜单处理例程(Context Menu Handler),选中上下文菜单中定制的菜单项后,也会调用相关的函数接口用于处理菜单选择事件。
Shell扩展程序DLL应包括如下导出函数。
◇DllMain:标准的DLL入口函数。
◇DllGetClassObject:这个导出函数的作用是给出类厂(class factory)接口。
◇DllCanUnloadNow:表示该DLL是否仍然在使用,系统是否可以卸载此DLL。和其他所有COM程序一样,Shell扩展程序必须实现一个IUnlnown接口和一个类厂。大多数情况下还需要实现一个IPersistFile或者IShellExtInit接口。
2、ProgID及特殊ProgID
每一种文件类型都对应一个ProgID,ProgID代表了一种文件类型。一些特殊的ProgID如表所示。
ProgID的shellex子键指明了ProgjD对应的处理例程。这些子键包括ColumnHandlers(使用“详细信息”方式查看文件列表,显示列时调用)、ContextMenuHandlers(右键菜单时调用)、CopyHookHandlers(移动、复制、删除、重命名等操作时调用)、DragDropHandlers(右键拖曳菜单时调用)、PropertySheetHandlers(查看Shell对象属性时调用)、DataHandler、DropHandler、IconHandler(显示图标时调用,用于个性化图标)等。
3、CLSID,处理例程的GUID
GUID(Global unique identifier,全局唯一标识符)用于在系统中唯一标识一个对象。CLSID是GUID在注册表中的表示,用于在注册表中用于唯一标识一个COM对象。
CLSID可以有很多子键,在Shell扩展中常用到的是InprocHandler32和InprocServer32这两个子键,分别用于向Shell注册处理例程DLL和in-process服务DLL。
关于具体的例子,可以参见[3]。
4、COM
Shell扩展程序是COM(Component Object Model,组件对象模型)程序。
COM是一个平台无关、分布式、面向对象的程序接口标准,一般用于扩展应用程序的功能。
大多数微软公司开发的应用程序都支持COM接口扩展,COM程序一般是DLL文件(ActiveX程序是.ocx扩展名的文件)。
COM程序被提供给主调程序调用。COM程序为主调程序提供固定的接口(DllGetClassObject导出函数)主调程序先通过固定的函数接口来获得其他的接口。Shell扩展中通过在注册表中配置的情况来在不同情况调用不同的接口函数。不同的COM程序具有不同的接口,但是所有的接口都是从类厂(Class Factory)和IUnknown接口获得的。所以COM程序必须实现类厂和IUnknown接口。
实际上,类厂和IUnknown接口所返回的其他接口,都是以函数指针的形式返回的。因此任何具有结构体指针,并可以通过函数指针来调用函数的程序语言都可以用来编写COM程序。
只有理解了Shell扩展作为COM程序的一种所必须遵守的接口规范才可能理解Shell扩展程序的工作原理。
由于COM程序是由其他程序调用的,因此COM程序存在一个主调程序。如果COM是对Shell的扩展,那么主调程序就是Explorer.exe,如果COM程序是对IE浏览器的扩展,那么主调程序就是iexplore.exe。
5、相关的几个API
1)DllGetClassObject函数的主要功能是为主调程序(COM程序都是实现好后供其他程序调用的,调用COM程序的程序就是COM程序的主调程序)返回调用接口。
2)IclassFactory COM程序发布实现IClassFactory接口。IClassFactory接口的作用是根据主调程序的要求返回本COM程序所实现了的接口,供主调函数调用。IClassFactory接口必须要实现两个成员函数,CreateInstance和LockServer,主调程序会调用这两个成员函数。CreateInstance成员函数由主调程序调用,作用实例化主调程序所需要的接口,并返回接口实例。
3)Iunknown。
任何一个COM程序都必须要实现两个接口,一是IClassFactory另外一个就是Iunknown。
IUnknown需要有3个成员函数:QueryInterface、AddRef和Release。
6、QueryInterface的功能与其他接口类似,AddRef和Release分别用于增加和减少COM对象的引用次数。当COM被引用及被释放时,主调程序将会调用这两个接口。
7、Shell扩展程序必须实现IShellExtInit接口。IShellExtInit接口应该具有成员函数Initialize,Initialize函数用于初始化Shell扩展程序所实现的属性页扩展处理例程、上下菜单处理例程、拖拽处理例程等。此成员函数在Shell需要弹出上下文菜单或弹出属性页前会被调用,以实例化相关接口。
参数pIDFolder是指向ITEMIDLIST类型的变量,用于标识哪个对象的菜单将会被弹出。如果是属性页扩展,那么此参数为NULL;如果是上下文件菜单扩展,那么这个参数代表了所需弹出菜单的对象所在的目录。
参数pDataObj指向IDataObj ect接口,用于获取操作的对象。
参数hkeyProgID是表示目录类型的注册表值。
1)在一般隋况下,Initialize成员函数需要根据参数所指定的扩展类型来实例化不同的Handler接口,比如对Context Menu Handlers就应用实例化IContextMenu接口。
IContextMenu需要实现GetCommandString、InvokeCommand和QueryContextMenu这3个成员函数。
当Shell需要显示上下文件菜单时,会调用QueryContextMenu函数。QueryContextMenu函数应当设置菜单中的项。GetCommandString用于向主调程序返回与菜单项关联的字符串。当用户点击上下文菜单中的项后,InvokeCommand会被调用。InvokeCommand函数的实现中,应该处理菜单项被点击后所应该执行的动作。
2)实现属性页的扩展需再实现IShellPropSheetExt接口,Ishell Prop SheetExt接口包括两个成员函数:AddPages和ReplacePage。
主调程序会根据注册表中的配置调用AddPages和ReplacePage,分别实现了属性页的增加和替换。属性页实质是一个对话框。
3)要在Shell扩展中实现图标的扩展,需要实现IExtractIcon接口。
IExtractIcon需要实现Extract和GetIconLocation两个成员函数。在GetIconLocation函数中调用了GetPrivateProfileInt来获得文件中配置的内容。GetIconLocation函数通过piIndex参数和szIconFile参数返回,表示应该使用szIconFile所指明的文件中的piIndex号图标。
4)如果在注册表中注册了CopyHookHandlers,那么在用户操作与文件类型相关的文件或文件夹时,ICopyHook接口CopyCallback成员函数将被调用。
参考
[1] 精通Windows API 函数、接口、编程实例
[2] http://msdn.microsoft.com/en-us/library/bb773177%28VS.85%29.aspx
[3] http://www.cnblogs.com/mydomain/archive/2010/12/03/1895050.html