参考资料一:
在Windows中我们经常需要遍历一个文件夹或者遍历一个磁盘。本文介绍如何使用标准的Shell接口进行遍历。在介绍过程中会逐步的实现一个类似FileZilla的TreeView+ListView的界面。我最近为psftp做界面的时候简单了解了一下这方面的问题。
Windows中的目录可以理解为是一个树型结构,树的根是“桌面”,“桌面”中一般拥有“我的电脑”、“网上邻居”、“回收站”等文件夹。这个我们称它为命名空间。物理上呢,“桌面”一般位于“C:/Documents and Settings/用户名称/桌面”。
IShellFolder接口用于管理文件夹,所有Shell命名空间中的文件夹对象都暴露这个接口。我们可以通过SHGetDesktopFolder方法获得“桌面”的IShellFolder接口。当然,最后不使用的时候要调用IShellFolder的Release方法释放接口。
通过调用“桌面”的IShellFolder接口的EnumObjects方法获得IENUMIDLIST接口的指针。IENUMIDLIST接口用于遍历IShellFolder接口表示的文件夹下的所有对象(这里的对象是指文件或者文件夹)。通过IENUMIDLIST接口可以遍历子对象的ITEMIDLIST数组。ITEMIDLIST数组表示一个对象的绝对或者相对路径。当前可以将ITEMIDLIST数组理解为给Shell使用的,代替我们常用的“WINDOWS/system32”这样的路径表示形式。而这里通过IENUMIDLIST接口遍历获得的IENUMIDLIST数组是一个相对路径,相对于当前IShellFolder的路径。调用IShellFolder的BindToObject方法,并传递IENUMIDLIST数组的相对路径,可以获得IENUMIDLIST数组表示的子文件夹的IShellFolder接口。这样我们可以通过递归或者循环遍历以“桌面”开始的整个逻辑目录树。
下面详细介绍一下上面提到的Shell使用的路径。Shell管理的文件和文件夹有存储在磁盘上的,也有不存储在磁盘上的,如“网络打印机”、“网络邻居”、“控制面板”、“回收站”等。这些不存储在磁盘上的文件或文件夹成为虚拟对象。像“网络打印机”这样的虚拟对象,根本不需要存储在磁盘上,它只存储几个网络打印机的链接。而像“回收站”这样的虚拟对象,它位于磁盘上,但需要进行与普通文件或文件夹不同的操作。例如,虚拟对象可能在Explorer中显示的是两个对象,但它们可能被存储在同一个磁盘文件中。
在文件系统的命名空间中,包含两种对象:文件夹对象和文件对象。文件夹对象是树的节点,它包含文件和子文件夹。文件对象是树的叶子,它可能是一个磁盘文件也可能是一个虚拟对象。如果一个文件夹不是文件系统的一部分,它通常被称为虚拟文件夹。
在使用命名空间中的对象前,我们必须先标识它。由于在文件系统中文件名是可以重复使用的,所以我们使用完整限定名(完整路径),如:“C:/MyDocs/MyFile.htm”。但是这无法表示虚拟对象。所以Shell使用一种替代的标识,这个标识能够表示命名空间中所有的对象。
在一个文件夹中,每一个对象都有一个item ID,它等价于文件或文件夹名称的功能。item ID实际上是一个SHITEMID结构:
typedef struct _SHITEMID
{
USHORT cb;
BYTE abID[1];
} SHITEMID, * LPSHITEMID;
其中abID成员是对象的标识符。abID的长度没有定义。它的值由包含它的文件夹来探测。abID的大小是可变的,所以cb成员存储SHITEMID结构的字节数。
因为item ID不是用于显示,所以包含它的文件夹通常为它分配一个“显示名称”。这个“显示名称”由Windows Explorer用来显示一个文件夹的内容。
Item ID很少单独使用,它通常是item ID列表中的一部分。item ID 列表与系统路径意义相同。但item ID 列表不是一个字符串,而是一个ITEMIDLIST结构,这个结构是一序列的item ID(一个或者多个),并由2个字节的NULL表示结束。item ID 列表中的每一个item ID 都对应命名空间中的一个对象。它们的次序表示命名空间中的路径,这很像文件系统路径。下面的图表显示了对应于“C:/MyDocs/MyFile.htm”的ITEMIDLIST的结构:
命名空间对象通常由ITEMIDLIST结构的指针来标识,或者指向一个item ID 列表的指针(PIDL)。为了方便,以后使用PIDL表示ITEMIDLIST结构,而不是item ID 列表的指针。上面图表显示的PIDL被称为“全的”或“完整的”PIDL。一个全的PIDL是由“桌面”开始,包含所有中间的路径的item ID。
全PIDL不常被使用。很多函数和方法使用相对PIDL。相对PIDL的根是一个文件夹,不是“桌面”。虽然它不是一个对象的唯一标识符,但是它要比全PIDL短,并且对于某些应用来说它能够充分说明该对象。
最常见的相对PIDL是单层PIDL,它相对于这个对象的父文件夹。它仅包含这个对象的item ID以及NULL结束符。多层的PIDL通常包含两个或更多的item ID,并且表示出了从父文件夹到这个对象的路径,这个路径中会包含中间的一些子文件夹。注意,单层PIDL也可能是一个全PIDL(如“我的电脑”相对“桌面”的相对PIDL)。特殊的,“桌面”对象是“桌面”的子文件夹。
虽然PIDL与系统路径很相似,但是它们还是有一些不同。主要的不同是如何分配和销毁它们的内存。在应用中,通常是系统分配PIDL使用的保持item ID的内存,而用户释放它。
所以,我们必须使用IMalloc接口来分配和释放PIDL。可以调用SHGetMalloc来获取IMalloc接口指针,调用IMalloc::Alloc方法来分配内存,IMalloc::Free方法来释放内存。最后调用IMalloc::Release释放指针。
-----------------------------------------------
-----------------------------------------------
参考资料二:
一、几个概念(摘)
1.外壳名字空间:
在WINDOWS中又叫外壳名字空间(Shell Name Space).外壳名字空间是Windows下的标准文件系统,它大大扩展了Dos文件系统,形成了以“桌面”(Desktop)为根的单一的文件系统树,原有的C盘、D盘等目录树变成“我的电脑”这一外壳名字空间子树的下一级子树,而像“控制面板”、“回收站”、“网上邻居”等应用程序及“打印机”等设备也被虚拟成了外壳名字空间中的节点。另外,与DOS中物理存储只能和文件系统项一一对应这一点不同的是,一个实际目录在外壳名字空间中可以表现为不同的项。例如“我的文档”与“C:/MyDocuments”其实都指向“C:/My Documents”目录,但它们在外壳名字空间中是不同的项。
2. 外壳名字空间下的路径: PIDL
PIDL是一个元素类型为ITEMIDLIST结构的数组,数组中元素的个数是未知的,但紧接着数组末尾的必是一个双字节的零。每个数组元素代表了外壳名字空间树中的一层(即一个文件夹或文件),数组中的前一元素代表的是后一元素的父文件夹。由此可见, PIDL实际上就是指向一块由若干个顺序排列的ITEMIDLIST结构组成、并在最后有一个双字节零的空间的指针。所以PIDL的类型就被Windows定义为ITEMIDLIST结构的指针。
PIDL亦有“绝对路径”与“相对路径”的概念。表示“相对路径”的PIDL只有一个ITEMIDLIST结构的元素,用于标识相对于父文件夹的 “路径”;表示“绝对路径”的PIDL(简称为“绝对PIDL”)有若干个ITEMIDLIST结构的元素,第一个元素表示外壳名字空间根文件夹(“桌面”)下的某一子文件夹A,第二个元素则表示文件夹A下的某一子文件夹B,其余依此类推。这样绝对PIDL就通过保存一条从“桌面”下的直接子文件夹或文件的绝对PIDL与相对PIDL是相同的,而其他的文件夹或文件的相对PIDL就只是其绝对PIDL的最后一部分了。由于所有的PIDL都是从桌面下的某一个子文件夹开始的,所以对于桌面本身来说,它的PIDL数组显然一个元素都没有。这样就只剩下PIDL数组最后的那个双字节的零了。所以,“桌面”的 PIDL就是一个16位的零。
二、几个API
1.HRESULT SHGetSpecialFolderLocation(
HWND hwndOwner,
int nFolder, //CSIDL
LPITEMIDLIST *ppidl //返回CSIDL所对应的绝对PIDL(输出)
);
2.DWORD_PTR SHGetFileInfo(
LPCTSTR pszPath, //uFlags含SHGFI_PIDL时为绝对PIDL(输入)
DWORD dwFileAttributes, //绝对PIDL所对应文件的 file attribute flags (输出)
SHFILEINFO *psfi, //返回file information
UINT cbFileInfo, //SHFILEINFO结构大 UINT uFlags //指定你所要获取的信息
); //该函数返回系统HIMAGELIST
说明:
typedef struct _SHFILEINFO {
HICON hIcon;
int iIcon; //图标索引
DWORD dwAttributes; //文件属性
TCHAR szDisplayName[MAX_PATH]; // 显示名称
TCHAR szTypeName[80]; //文件类型:一个值对应一个二进制位
} SHFILEINFO;
uFlags :SHGFI_DISPLAYNAME | SHGFI_TYPENAME :psfi返回中包含显示名称和文件类型 (其他类推)
3.HRESULT SHGetDesktopFolder(
IShellFolder** ppshf //f返回桌面IShellFolder接口
);
IShellFolder的几个方法:
(1).BingToObject 获取子文件夹的IShellFolder接口
(2).EnumObject获取IEnumIDList ,IEnumIDList ->Next方法获取子文件的相对PIDL(详见MSDN)
(3)GetAttributesOf获取文件属性
注意事项:参数中是绝对PIDL还是相对PIDL。SHGet绝对,IShellFolder相对。PIDL用IMalloc接口开辟空间
-----------------------------------------------------------------
-----------------------------------------------------------------
参考资料三:
使用IShellFolder接口
前面的文档中,名字空间文件夹被称作“对象”。这个术语的使用好像不太严格,但实际上从严格意义上讲,这个术语也是合适的,因为每个名字空间文件夹都由一个COM对象表示。每个文件夹对象实现了很多可以用于各种任务的接口。有些接口是可选的,不是每个文件夹都实现了,但每个文件夹都必须实现基本接口IShellFolder。
要使用文件夹,首先需要获取其IShellFolder接口指针。IShellFolder接口指针除了可以提供对对象其他接口的访问外,还提供了处理很多通常任务的方法,本文将讨论某些通常任务。
要获取Shell对象的IShellFolder接口指针,首先调用SHGetDesktopFolder,它会返回名字空间根,即桌面的IShellFolder接口指针。获取了桌面对象的IShellFolder接口指针后,有很多方法进行下一步。
如果已经有要处理的文件夹的PIDL了(比如说,通过SHGetFolderLocation获取的),可以用桌面对象的(IShellFolder接口指针的)BindToObject方法获取文件夹的IShellFolder接口指针。如果已经有文件系统对象的路径了,可以先用桌面对象的ParseDisplayName方法获取其PIDL,然后再调用BindToObject。如果这两种方法都不可用,可以用IShellFolder的其他方法浏览名字空间(并定位到要处理的文件夹),详情请参考Navigating the Namespace。
枚举文件夹内容
文件夹处理的第一件事通常是看看它包含什么内容,这时可以先调用文件夹的EnumObjects方法。方法会创建标准OLE枚举对象并返回其IEnumIDList接口。枚举接口含有4个标准方法:Clone、Next、Reset、Skip。这些方法可用于枚举文件夹内容。
枚举文件夹内容的基本过程是:
注意:要注意当前使用的是相对PIDL还是全限定PIDL。某些函数可以接受两种类型的PIDL,但是有些却只能接受一种类型的。IEnumIDList的另外3个方法可用于重复枚举文件夹内容。这3个方法可以复位枚举过程、跳过一个或者多个对象、制作枚举对象的副本以保存其状态。
确定显示名和其他属性
在枚举了文件夹包含的所有PIDL后,就可以发现它们所代表的对象类型。IShellFolder接口提供了很多有用的方法,这里只讨论两个。IShellFolder接口的其他方法和其他Shell文件夹接口在本系列后面的文章中讨论。
显示名是对象的最重要属性之一。要获取对象的显示名,把PIDL传递给GetDisplayNameOf即可。对象可以位于名字空间中父文件夹以下的任何地方,但PIDL必须是相对于父文件夹的。
GetDisplayNameOf把显示名作为STRRET对象的一部分返回。因为从STRRET结构取出显示名需要一点技巧,所以Shell为你提供了两个函数,StrRetToStr和StrRetToBuf。这两个函数都需要一个STRRET结构体,把显示名作为普通字符串返回。它们的不同在于字符串是如何分配的。
除了显示名外,对象还可以有很多其他属性,比如说,对象是否是文件夹,是否可以被移动。把对象的PIDL传给GetAttributesOf就可以获取其属性。完整的属性列表很长,细节可以参考相关文档。注意传递给GetAttributesOf的PIDL必须是单层的。特别是,GetAttributesOf可以接受Next返回的PIDL。可以传入一个PIDL数组,GetAttributesOf会返回数组中所有对象共有的属性。
如果有对象的全限定路径名或者PIDL,SHGetFileInfo提供了一种获取足够用于多种目的的对象信息的简单方法。SHGetFileInfo返回的信息包括:
获取子文件夹的IShellFolder接口指针
调用GetAttributesOf,检查SFGAO_FOLDER标志是否已设置就可以确定文件夹是否含有子文件夹。如果对象是文件夹,就可以绑定到它,这可以提供对象的IShellFolder接口。
要绑定到文件夹,调用父文件夹的BindToObject方法。方法要求传入子文件夹的PIDL,并返回其IShellFolder接口指针。有了这个指针后,就可以枚举文件夹的内容,确定其属性等。
确定对象的父文件夹
如果已有某对象的PIDL,可能需要其父文件夹暴露的某个接口。比如说,如果想用GetDisplayNameOf确定父文件夹的显示名,必须先获取父对象的IShellFolder接口。可以用上一节讨论的技术来达到目的,但有一种更简单的方法,就是使用SHBindToParent方法。这个方法要求传入对象的全限定PIDL,并且返回父文件夹的特定接口指针。SHBindToParent还可以返回对象的单层PIDL以用于GetAttributesOf等方法。
转载自:
http://blog.csdn.net/liutaoxwl/article/details/3935861
http://blog.csdn.net/liutaoxwl/article/details/3932219
http://blog.csdn.net/ooolxf/article/details/1776893
http://blog.sina.com.cn/s/blog_56dee71a0100frhy.html