即 Windows 下的命名空间扩展
命名空间扩展
一种允许将外部自定义的信息集成到windows资源管理器,以用户自定义显示方式来处理数据信息,资源管理器提供必要的控制、交互接口、GUI实现。
实现命名空间扩展
- 一个文件件管理器对象,用于请求其其需要的信息
- 一个显示文件夹内容的视图窗口
- 一个枚举文件夹内的项目的枚举对象
- 一个表征文件夹对象的标识ID
- 一系列可定制用户GUI的访问函数
一般流程
命名空间扩展和shell扩展几乎一样,都要被注册安装,被检测、被调用,都属于进程里COM服务实现,实现一系列的接口来定制Shell;不同点:前者主要是针对Explorer的添加一个虚拟文件夹,后者则是限制于文件类型。
- 资源管理器扫描注册表来安装组件并建立和他们的连接通信,无论是你自己实现或是系统自带的;
- 资源管理器探测到当前存在的命名空间扩展,它便加载该COM服务对象(调用IShellFolder的一些接口实现);
- IShellFolder作为一个文件夹管理器,并提供给资源管理器需要的任何东西,也即是充当资源管理器和扩展的其他部分之间的代理对象;
- 当Explorer需要显示一个视图内容时,Explorer将请求IShellFolder一个视图对象,同样的,当显示树形视图的节点时,其将请求枚举文件夹对象和子文件夹的属性内容;这些几乎均通过IShellFolder接口来完成;
- 当命名空间扩展被加载后,资源管理器也会给其一个机会来更新用户界面。所有的可能扩展对象感兴趣的事件都将会被通告调用到相应的函数实现。也就是说,实现一个命名空间扩展也就是要准备响应Explorer的各种请求实现,而响应实现则通过具体的COM接口来实现(一些必要的接口集合实现)。
实现命名空间扩展必要的接口
- IShellFolder
- IPersistFolder
- IEnumIDList
- IShellView
前两者可认为为文件管理器Folder Manager,IEnumIDList为枚举器,IShellView则为视图窗口。
另外可选的接口:IContextMenu和IExtractIcon用来请求自定义的上下文菜单和单个Item项的图标。
对于一些接口实现,若自己不想实现则可交给Explorer知道的,可通过返回E_NOTIMPL的错误码值。
PIDL:一个文件夹下的一个项目的标识符,其将贯穿于整个Shell的命名空间;其表示一个文件夹类型,对于自定义的文件夹,则也需要提供自定义的PIDL。
具体流程
获取一个命名空间扩展显示到explorer的流程
- Explorer检测到命名空间扩展通过注册表结点获取它的CLSID;
- Explorer创建该命名扩展对象实例,并请求IShellFolder接口;
- Explorer请求该对象(实现了IShellFolder)返回一个IShellView接口对象的指针在一个视图对象上;
- 一个IShellBrowser指针传给该视图对象,并允许该IShellBrowser对象来控制Explorer的菜单和工具栏;
- 另外该视图对象也会收到一个IShellFolder的对象指针;
- Explorer请求IShellFolder对象返回一个可以枚举文件夹内容的对象;
- Explorer遍历该文件夹包含的元素列表。对于每个元素获取它的PIDL并根据它的角色和特点来绘制它。
点击Tree View的某个文件夹时的操作流程
- Explorer请求IShellFolder返回一个可枚举文件夹的对象(一个枚举器);
- Explorer仅显示有“folder”属性的元素,对于还有“has subfolders”属性的,会绘制一个“+”或者其他可展开的节点;
- Explorer请求IShellFolder提供显示Tree View中每个节点的图标;事实上,Explorer将接收一个IExtractIcon接口的对象指针;
- Explorer请求IShellFolder提供下一个被显示的各Item项;
- Explorer请求IShellFolder提供对每个Item项的上下文菜单。
Folder Manager
- IshellFolder继承于IPersistFolder, IPersistFolder提供Explorer来初始化一个文件夹对象,告诉explorer在命名空间中的位置
- IShellFolder实现的一些接口提供给Explorer来请求一个视图View、一个枚举对象,一个子文件夹对象
- IShellFolder对象还需要提供其包含的每个单独项的属性信息,如比较两个Item项,返回它们的显示名称,这些Item项均通过PIDLs来定义
主要的接口说明
IPersistFolder
GetClassID:获取当前文件夹对象的CLSID,该CLSID为GUID的常量字符串,为命名空间扩展的CLSID值。
Initialize:允许文件夹初始化自身,参数pidl为文件夹所在命名空间的位置,若是与当前文件夹相关,则可先保存后面用;否则返回S_OK即可。
注意:这些接口不能被你调用,其主要是被绑定到你的文件夹后,由系统来调用的。
IShellFolder
BindToObject:shell请求模块打开一个子文件夹时的调用,该方法提供一个PIDL以及你需要创建一个基于该PIDL的新的文件夹对象返回。
BindToStorage:Shell不再调用该接口,可直接返回E_NOTIMPL。
CompareIDs:主要提供两个Item比较和排序用的,参数为PIDL。
CreateViewObject:创建并返回一个IShellView对象,用以右侧面板显示视图内容的。
EnumObjects:创建并返回一个IEnumIDList对象,用以枚举文件夹下对应的各项的。
GetAttributesOf:返回一个特定项的属性族,无论其是否被重命名或拷贝、或持有的图标、或是一个文件夹或有子文件,
这些属性都是由SFGAO_xxx开头的一些助记符。
GetDisplayNameOf:返回文件夹中的一个用于渲染一个项的名称,如在地址栏、用于解析等(见SHGNO枚举含义)。
GetUIObjectOf:借助于该方法,Explorer请求一个特殊的接口来处理UI。
ParseDisplayName:返回一个给定PIDL的显示名称,显示名称并不必要,当设置SHGDN_FORPARSING标识时,由GetDisplayNameOf返回。
SetNameOf:给一个给定对象分配一个新的显示名称,这个名称主要用于地址栏,文件夹,或解析目的。
很多情况下,显示名称与实际的文件名称是一致的,不管是否文件夹包含文件
说明:CreateViewObject:获取IShellView;GetUIObjectOf:获取IContextMenu和IExtractIcon;EnumObjects:获取IEnumIDList。
IEnumIDList
为了允许外部模块遍历自定义文件夹的内容,命名空间扩展需要实现IEnumIDList接口,该对象接口提供给其他模块可枚举任何文件夹的各项内容;此外该接口比较通用,一个模块和它通信不需要知道任何它的内容或者文件夹对象自身的组织方式
Next:返回集合中特定数量的项目;每个项目通过PIDL被定义。
Skip:跳过特定数量的项目。
Reset:移动当前指针到list列表的头部。
Clone:拷贝当前对象副本。
其中最为重要的便是Next接口;用于枚举对象获取指定数量的PIDL值,一般建议用一个指针保存最新将要返回的值的位置。
一般数据存储结构(以LPENUMLIST形成的链表结构)。
LPITEMIDLIST(PIDL) = ITEMIDLIST + USER_STRUCT_DATA + ITEMIDLIST(占位的,作为单个PIDL指针内容结束的哨兵);USER_STRUCT_DATA 可为任何形式的内容,一般我们称之为PIDLDATA(或任何名称均可);
第一个ITEMIDLIST(也即是SHITEMID)中cb为ITEMIDLIST + USER_STRUCT_DATA的大小,而abID地址位置值即为实际的存储数据(类似于让USER_STRUCT_DATA前移一个BYTE)或者USER_STRUCT_DATA位置放实际的值也可以(有个冗余的1BYTE字节) LPENUMLIST = PIDL + next;
注意:
- PIDL分配的资源以及本身均应从IMalloc接口来申请,使得Explorer可释放它;PIDL只是一块内存不是对象。
- PIDL可以存储到磁盘,此时PIDL内部内容则不应该含有指针或引用之类的。
- 如果你要PIDL持久化,则建议增加版本或签名之类的字段,这样可以向后兼容。
IShellView
右侧视图中放置的一个IShellView视图对象,其与Explorer通信,以处理消息,菜单、工具栏等
AddPropertySheetPages:允许你添加文件夹选项的自定义页对话框。
CreateViewWindow:创建并返回一个嵌入到右侧面板的窗口,其应该为一个无边框的窗口。
DestoryViewWindow:销毁前一个窗口。
EnableModeless:一般不用,直接返回E_NOTIMPL。
EnableModelesssSV:同上。
GetCurrentInfo:通过一个FOLDERSETTINGS结构,返回当前文件夹的配置。
GetItemObject:返回一个上下文菜单或剪切板的一个指针接口,一个项目的集合,一般被共用的对话框调用。
Refresh:使得当前文件夹内容被重绘。
SaveViewState:保存当前视图的状态(可结合IShellBrowser的GetViewStateStream接口获取到ISteam来操作数据保存持久化);此外,IStream需要操作的Read和Write的对象由传给GetViewStateStream的参数STGM_READ和STGM_WRITE分别来表示读写数据流。
SelectItem:修改当前的一个或多个项目的选择状态。
TranslateAccelerator:当扩展获取到焦点时,翻译快捷键;返回S_OK可阻止explorer再次翻译。
UIActivate:当激活状态被改变时候被调用,如当文件夹被激活或失去激活,一般修改某些状态均可在此函数中完成。
GetWindow:返回当前视图窗口的句柄。
ContextSensitiveHelp:文件夹应进入或退出语境上下文帮助模式,并处理所有的不同的消息,一般也不常用。
IShellBrowser
实现Explorer与命名空间扩展的通信交互,如菜单、工具栏、视图状态等
菜单
处理IShellView中的UIActivate(),当文件视图从未激活到激活时,需先移除早期的菜单即通过IShellBrowser的RemoveMenusSB以及DestoryMenu销毁资源,再由CreateMenu创建并调用IShellBrowser的InsertMenusSB接口,让shell通过菜单组描述来共享该菜单;
此后便可通过InsertMenuItem或DeleteMenu等处理自己的菜单内容;处理后调用IShellBrowser的SetMenuSB接口将该菜单对象设置到当前命名空间扩展,(另外需要保存一下当前的激活与否的状态,对于状态没有变化时,则无需处理前面的一系列操作),菜单ID对应的消息也会被发给右侧的视图对象,视图对象自行处理即可。
工具栏
IShellBrowser的SendControlMsg,第一个参数为FCW_TOOLBAR即为操作工具栏;若注册图标按钮,则第二个参数为TB_ADDBITMAP(对应第四个参数为一个TBADDBITMAP对象地址)或其他如TB_ADDSTRING(第四个参数为字符串地址);
IShellBrowser的SetToolbarItems设置按钮,参数为TBBUTTON结构对象,以及FCT_MERGE参数。另外还有SendControlMsg工具栏FCW_TOOLBAR下的其他消息参数如TB_GETBUTTON,TB_DELETEBUTTON或TB_INSERTBUTTON等。
状态栏
IShellBrowser的SendControlMsg,第一个参数为FCW_STATUS即为状态栏,灵活性比较大;若是简单的字符串,则可用SetStatusTextSB即可。
左侧树控件
IShellBrowser的SendControlMsg,第一个参数为FCW_TREE,即为给左侧树控件面板发送消息。
IContextMenu
自定义上下文菜单,右键菜单
InvokeCommand:
QueryContextMenu:
GetCommandString:
IExtractIcon
自定义图标,下面两个均为返回图标,前者为返回图标路径和索引后面再提取,后者直接提取返回HICON;两者是相互排斥独立的,其中一个调用成功另一个便不会被调用
GetIconLocation:
Extract:
这个主要用来设置地址栏和左侧树控件面板的图标,至于视图的如列表控件则需要自行实现。除了IExtractIcon,IShellIcon也可以提供图标而且比较快速;
另外,前者是需要的时候还要单独创建一个图标实例;而后者只需要提前准备好创建一次即可。故而Explorer搜索树或地址栏图标时,优先从IShellIcon中获取;若失败,再从IExtractIcon的GetUIObjectOf获取。
IDropTarget
支持拖拽
DragEnter:
DragOver:
DragLeave:
Drop:
IDataObject
支持拖拽以及拷贝至剪切板或打包数据,并提供多种数据格式、可移动
其他部分扩展接口
IShellView2:
IPersistFolder2:
命名空间扩展节点(Junction Points)
注册注册表节点,因其为COM进程里服务,故需要被注册到HKEY_CLASSES_ROOT\CLSID
访问命名空间扩展的节点,操作方式可有:
- 关联命名空间到一个文件类型
- 使用一个非常特别内容的目录
- 使用一个特别名称的目录
- 将其关联到一个已存在的命名空间上
安装命名空间扩展
- 注册命名空间扩展为COM服务对象,并指定其线程模型为apartement,显示图标、扩展名称;
- 注册approved扩展,以支持WindowsNT系统下可以工作;
- 定义你的扩展节点;
一般注册节点包含有:
- HKEY_CLASSES_ROOT\CLSID{XXX-XXX....}设置默认值为扩展名称
- HKEY_CLASSES_ROOT\CLSID{XXX-XXX....}\InProcServer32下设置默认值dll以及ThreadingModel=Apartment字符串
- HKEY_CLASSES_ROOT\CLSID{XXX-XXX....}\DefaultIcon下默认值设置为"xxx.dll,0",(0为图标索引,可根据需要选择图标)
- HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\ShellExtensions\Apprved{XXX-XXX....}下设置NT下支持的扩展名称,一般可和上名称一样
另外如果需要增加到桌面、我的电脑图标,则可添加以下注册表:
- HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace{XXX-XXX...};
- HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace{XXX-XXX...};
除了以上的,还可以增加HKEY_CLASSES_ROOT\CLSID{XXX-XXX....}\ShellFolder的Attributes十六进制值,该值一般为SFGAO_FOLDER|SFGAO_HASSUBFOLDER等值的组合值,根据你的需要设置该Attributes值。
HKEY_CLASSES_ROOT\CLSID{XXX-XXX....}可增加一个InfoTip值,使得当鼠标放在图标上时,可以显示tootip提示信息;虽然此信息也可以通过IQueryInfo的接口来实现。
想要给桌面的那个图标增加一个移除命名,可以增加一个Removal Message的字符串,并且要在之前的那个Attributes上组合一个SFGAO_CANDELETE属性即可(移除了桌面的图标后,也会自动异常对应注册表值)。
还可以添加其他更多的属性在上面,如允许重命名和属性,前者需要一个SFGAO_CANRENAME,后者需要SFGAO_HASPROPSHEET并实现ISehllPropShetExt接口,另外还要在HKEY_CLASSES_ROOT\CLSID{XXX-XXX....}下增加一个Shellex\PropertySheetHandlers键值,值即为该接口对应的GUID。
要让命名空间扩展支持系统公共接口或对话框等之类也存在时候,需要组合SFGAO_BROWSABLE,SFGAO_FILESYSTEM的Attributes值并且可能需要SHBrowserForFolder函数的实现支持。
其他:
注册命名空间扩展时,可能需要硬编码DllRegisterServer函数;当被regsvr32.exe调用启动该扩展时,将立即生效刷洗资源管理器,你的扩展就可以生效了。