Windows下使用标准Shell接口遍历文件和文件夹(1)

来源:互联网 发布:淘宝买二手苹果6 编辑:程序博客网 时间:2024/05/16 09:07

在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命名空间(翻译自MSDN)

简介

下面详细介绍一下上面提到的Shell使用的路径。Shell管理的文件和文件夹有存储在磁盘上的,也有不存储在磁盘上的,如“网络打印机”、“网络邻居”、“控制面板”、“回收站”等。这些不存储在磁盘上的文件或文件夹成为虚拟对象。像“网络打印机”这样的虚拟对象,根本不需要存储在磁盘上,它只存储几个网络打印机的链接。而像“回收站”这样的虚拟对象,它位于磁盘上,但需要进行与普通文件或文件夹不同的操作。例如,虚拟对象可能在Explorer中显示的是两个对象,但它们可能被存储在同一个磁盘文件中。 
      在文件系统的命名空间中,包含两种对象:文件夹对象和文件对象。文件夹对象是树的节点,它包含文件和子文件夹。文件对象是树的叶子,它可能是一个磁盘文件也可能是一个虚拟对象。如果一个文件夹不是文件系统的一部分,它通常被称为虚拟文件夹。

标识命名空间中的对象

在使用命名空间中的对象前,我们必须先标识它。由于在文件系统中文件名是可以重复使用的,所以我们使用完整限定名(完整路径),如:“C:/MyDocs/MyFile.htm”。但是这无法表示虚拟对象。所以Shell使用一种替代的标识,这个标识能够表示命名空间中所有的对象。

Item IDs

在一个文件夹中,每一个对象都有一个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 列表与系统路径意义相同。但item ID 列表不是一个字符串,而是一个ITEMIDLIST结构,这个结构是一序列的item ID(一个或者多个),并由2个字节的NULL表示结束。item ID 列表中的每一个item ID 都对应命名空间中的一个对象。它们的次序表示命名空间中的路径,这很像文件系统路径。下面的图表显示了对应于“C:/MyDocs/MyFile.htm”的ITEMIDLIST的结构:

无标题

PIDLs

命名空间对象通常由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与系统路径很相似,但是它们还是有一些不同。主要的不同是如何分配和销毁它们的内存。在应用中,通常是系统分配PIDL使用的保持item ID的内存,而用户释放它。 
      所以,我们必须使用IMalloc接口来分配和释放PIDL。可以调用SHGetMalloc来获取IMalloc接口指针,调用IMalloc::Alloc方法来分配内存,IMalloc::Free方法来释放内存。最后调用IMalloc::Release释放指针。

实现PIDL相关方法

我们提供几个方法来实现关于PIDL的处理:

   1: /**
   2:  * 创建一个新的PIDL。
   3:   * @param pIMalloc IMalloc接口指针。
   4:   * @param size 新的PIDL的字节数。
   5:   * @return 返回新创建的PIDL。
   6:   * 不再使用的时候需要调用IMalloc的Free方法释放。
   7:   */
   8:  LPITEMIDLIST pidl_create (IMalloc* pIMalloc, size_t size);
   9:  
  10:  /**
  11:   * 返回pidl所分配的总字节数。
  12:   * @param pidl 要计算的PIDL对象。
  13:   * @return 返回pidl所占用的字节数。
  14:   */
  15:  size_t pidl_size (LPCITEMIDLIST pidl);
  16:  
  17:  /**
  18:   * 获得PIDL中下一个ITEMIDLIST结构指针。
  19:   * @param pidl 要计算的PIDL。
  20:   * @return 返回pidl中下一个ITEMIDLIST结构指针。
  21:   */
  22:  LPCITEMIDLIST pidl_next (LPCITEMIDLIST pidl);
  23:  
  24:  /**
  25:   * 将两个PIDL连接起来。
  26:   * @param pIMalloc IMalloc接口指针。
  27:   * @param pidl_parent 要连接的PIDL。
  28:   * @param pidl 要连接的PIDL。
  29:   * @return 将两个PIDL连接后生成的PIDL。
  30:   * 不再使用的时候需要调用IMalloc的Free方法释放。
  31:   */
  32:  LPITEMIDLIST pidl_concat (IMalloc* pIMalloc, LPCITEMIDLIST pidl_parent, LPCITEMIDLIST pidl);
  33:  
  34:  /**
  35:   * 深度拷贝一个PIDL,并返回新创建的PIDL指针。
  36:   * @param pIMalloc IMalloc接口指针。
  37:   * @param pidl 要被复制的PIDL。
  38:   * @return 返回复制的新的PIDL。
  39:   * 不再使用的时候需要调用IMalloc的Free方法释放。
  40:   */
  41:  LPITEMIDLIST pidl_copy (IMalloc* pIMalloc, LPCITEMIDLIST pidl);
  42:  
  43:  /**
  44:   * 对比两个PIDL对象是否完全相同。
  45:   * @return 返回结果与memcmp一致。
  46:   */
  47:  int pidl_compare_all (LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2);
  48:  
  49:  /**
  50:   * 获取PIDL中最后一个ITEMIDLIST对象的指针。
  51:   * @param pidl PIDL对象的指针。
  52:   * @return 返回最后一个ITEMIDLIST结构的指针。
  53:   */
  54:  LPCITEMIDLIST pidl_last (LPCITEMIDLIST pidl);
LPITEMIDLIST pidl_create (IMalloc* pIMalloc, size_t size);

创建ITEMIDLIST数组。

   1: LPITEMIDLIST 
   2: pidl_create (IMalloc* pIMalloc, size_t size)
   3: {
   4:     LPITEMIDLIST pidl = NULL;
   5:     if (pIMalloc != NULL)
   6:     {
   7:         pidl = (LPITEMIDLIST)pIMalloc->lpVtbl->Alloc(pIMalloc, size);
   8:         if (pidl != NULL)
   9:         {
  10:             ZeroMemory (pidl, size);
  11:         }
  12:     }
  13:     return pidl;
  14: }
size_t pidl_size (LPCITEMIDLIST pidl);

获得某个PIDL使用的字节数。

   1: size_t 
   2: pidl_size (LPCITEMIDLIST pidl)
   3: {
   4:     size_t size = 0;
   5:     if (pidl != NULL)
   6:     {
   7:         size += sizeof (pidl->mkid.cb);
   8:         while (pidl->mkid.cb > 0)
   9:         {
  10:             size += pidl->mkid.cb;
  11:             pidl = pidl_next (pidl);
  12:         }
  13:     }
  14:     return size;
  15: }
LPCITEMIDLIST pidl_next (LPCITEMIDLIST pidl);

得到一个PIDL中指向下一个item ID的指针。

   1: LPCITEMIDLIST 
   2: pidl_next (LPCITEMIDLIST pidl)
   3: {
   4:     LPBYTE pb = (LPBYTE) pidl;
   5:     if (pidl != NULL)
   6:     {
   7:         pb += pidl->mkid.cb;
   8:     }
   9:     return (LPCITEMIDLIST)pb;
  10: }
LPITEMIDLIST pidl_concat (IMalloc* pIMalloc, LPCITEMIDLIST pidl_parent, LPCITEMIDLIST pidl);

合并两个PIDL,通常用于将父文件夹的全PIDL与它的子文件或文件夹对象的相对PIDL合并,从而得到文件或者子文件夹的全PIDL。

   1: LPITEMIDLIST 
   2: pidl_concat (IMalloc* pIMalloc, LPCITEMIDLIST pidl_parent, LPCITEMIDLIST pidl)
   3: {
   4:     size_t size1 = 0, size2 = 0;
   5:     LPITEMIDLIST pidl_new = NULL;
   6:  
   7:     if (NULL == pidl || NULL == pIMalloc)
   8:     {
   9:         return NULL;
  10:     }
  11:  
  12:     if (pidl_parent != NULL)
  13:     {
  14:         size1 = pidl_size (pidl_parent) - sizeof (pidl_parent->mkid.cb);
  15:     }
  16:     size2 = pidl_size (pidl);
  17:  
  18:     pidl_new = pidl_create (pIMalloc, size1 + size2);
  19:     if (pidl_new != NULL)
  20:     {
  21:         if (pidl_parent != NULL)
  22:             memcpy(pidl_new, pidl_parent, size1);
  23:         memcpy(((LPBYTE)pidl_new) + size1, pidl, size2);
  24:     }
  25:     return pidl_new;
  26: }
LPITEMIDLIST pidl_copy (IMalloc* pIMalloc, LPCITEMIDLIST pidl);

深度拷贝一个PIDL。

   1: LPITEMIDLIST 
   2: pidl_copy (IMalloc* pIMalloc, LPCITEMIDLIST pidl)
   3: {
   4:     LPITEMIDLIST lpi_tmp = NULL;
   5:     if (NULL == pIMalloc || NULL == pidl)
   6:     {
   7:         return NULL;
   8:     }
   9:  
  10:     lpi_tmp = (LPITEMIDLIST)pIMalloc->lpVtbl->Alloc(pIMalloc, pidl->mkid.cb+sizeof(pidl->mkid.cb));
  11:     CopyMemory((PVOID)lpi_tmp, (CONST VOID *)pidl, pidl->mkid.cb + sizeof(pidl->mkid.cb));
  12:  
  13:     return lpi_tmp;
  14: }
int pidl_compare_all (LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2);

对比两个PIDL是否完全相同,如果是两个全PIDL完全相同,则表示它们代表命名空间中同一个对象。

   1: int 
   2: pidl_compare_all (LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
   3: {
   4:     int us = 0;
   5:     if (NULL == pidl1 || NULL == pidl2)
   6:     {
   7:         return -1;
   8:     }
   9:  
  10:     us = pidl1->mkid.cb - pidl2->mkid.cb;
  11:     if (0 == us)
  12:     {
  13:         us = memcmp (&(pidl1->mkid.abID), &(pidl2->mkid.abID), pidl1->mkid.cb);
  14:     }
  15:     return us;
  16: }
LPCITEMIDLIST pidl_last (LPCITEMIDLIST pidl);

获得某个PIDL中最后一个item ID的指针。

   1: LPCITEMIDLIST 
   2: pidl_last (LPCITEMIDLIST pidl)
   3: {
   4:     LPCITEMIDLIST next = NULL;
   5:     
   6:     next = pidl_next (pidl);
   7:     while (next->mkid.cb > 0)
   8:     {
   9:         next = pidl_next (pidl);
  10:     }
  11:     return pidl;
  12: }

 

 

回顾IShellFolder接口

下面我们详细介绍一下IShellFolder接口的几个主要的方法:

BindToObject

HRESULT BindToObject( LPCITEMIDLIST pidl, LPBC pbc, REFIID riid, VOID** ppvOut );

我们通常使用该方法来获取当前IShellFolder接口的某个子文件夹的IShellFolder接口。 
      pidl是子文件夹的相对PIDL。 
      pbc通常是0. 
      riid要检索的子文件夹对象的接口ID,通常我们检索IShellFolder接口,所以该值通常是IID_IShellFolder。 
      ppvOut接收检索到的接口指针。

EnumObjects

HRESULT EnumObjects( HWND hwndOwner, SHCONTF grfFlags, IENUMIDLIST** ppenumIDList );

我们通常使用该方法获取IENUMIDLIST接口,IENUMIDLIST接口用于遍历当前IShellFolder表示的文件夹包含的文件或者子文件夹对象。 
      hwndOwner设置一个窗体句柄,如果要弹出提示信息(如“请插入光盘”)时,弹出的窗体是这个窗体的子窗体。 
      grfFlags参数表示要枚举哪些对象。 
      ppenumIDList用于接收IENUMIDLIST接口指针。

HRESULT GetAttributesOf( UINT cidl, LPCITEMIDLIST* apidl, SFGAOF* rgfInOut );

获得文件对象或者子文件夹的属性。例如,可以通过获得某些属性得知该IShellFolder是否是一个文件夹、是否有子文件夹。(如果有子文件夹,在TreeView中,这个节点应该可以被展开) 
      cidl通常是1. 
      apidl是一个相对与这个IShellFolder的相对PIDL。 
      rgfInOut是一个ULONG的值,作为输入和输出参数使用。作为输入参数来说,用它来设置调用者想要获得哪些属性。调用结束后,相应的属性位被置位并返回。例如,我们要查看这个IShellFolder接口代表的对象是否是一个文件夹,是否有子文件夹,我们应该这样做: 
      ULONG ulAttrs = SFGAO_HASSUBFOLDER | SFGAO_FOLDER; 
      pIShellFolder->GetAttributesOf (1, &pidl, &ulAttrs); 
      if (ulAttrs & SFGAO_FOLDER) 
      { 
            // 是文件夹 
      } 
      if (ulAttrs & SFGAO_HASSUBFOLDER ) 
      { 
            // 有子文件夹 
      }

HRESULT GetDisplayNameOf( LPCITEMIDLIST pidl, DWORD uFlags, LPSTRRET lpName );

用这个方法获得某个文件对象或者子文件夹的显示名称。 
      pidl要获得的文件对象或子文件夹的相对pidl。 
      uFlags要获得显示名称的类型。 
      lpName接收显示名称。可以使用StrRetToBuf函数将lpName转换成字符串。

C语言要注意的问题

由于接口对象都是用类的形式存在的,而C语言中没有类的概念,所以要在C语言中使用接口提供的方法需要做一点点额外的操作。在C语言中所有的接口指针实际上都是一个结构指针。所有的接口结构中都有一个lpVtbl成员这个成员是一个指向另外一个结构(struct *_lpVtbl)的指针。这个*_lpVtbl结构中的所有成员都是接口所提供的方法的函数指针。所以我们要想在C语言中调用某个接口的方法,应该这样调用: 
      pIShellFolder->lpVtbl->GetAttributesOf (pIShellFolder, 1, &pidl, &ulAttrs); 
      如果你自己要实现某些接口供Windows系统调用,记住,一定也要这样来实现。具体原理,这里就不讲了,看看C++方面的书吧。后面实例中都是使用C语言来实现的。

对Shell命名空间相关方法的封装

因为我们使用C语言实现,所以先定义需要用的的数据结构。我通常要使用有“桌面”的IShellFolder接口指针,还有IMalloc接口指针来释放或者分配内存。

   1: /**
   2:  * 操作Shell扩展接口的结构定义
   3:  * @struct shell_folder
   4:  */
   5: struct shell_folder
   6: {
   7:   IShellFolder* pDesktop; /**< 桌面IShellFolder接口指针 */
   8:   IMalloc* pIMalloc;      /**< IMalloc接口指针 */
   9: };

对于枚举某个目录的文件对象和子文件夹来说,我们需要传递一些信息给回调函数,初步计划,在回调函数中负责向TreeView或者ListView中添加节点。我们给出遍历一个IShellFolder接口的所有文件对象和子文件夹的参数结构:

   1: /**
   2:  * 枚举一个目录中所有文件时使用的数据对象。
   3:  * @struct sf_enum_data
   4:  */
   5: struct sf_enum_data
   6: {
   7:   LPSHELLFOLDER lpsfParent;    /**< 当前对象的父目录IShellFolder指针 */
   8:   LPITEMIDLIST lpi;            /**< 当前对象相对于父目录的相对PIDL */
   9:   LPITEMIDLIST lpifq;          /**< 当前对象相对于桌面的绝对PIDL */
  10: };

遍历用的回调函数类型声明:

typedef BOOL(*sf_cb_enum_func)(struct shell_folder* sf, struct sf_enum_data* data, void* args);

下面给出需要的函数:

   1: /**
   2:  * 创建shell_folder结构对象指针。
   3:  * @return 返回创建的shell_folder结构指针。
   4:  */
   5: struct shell_folder* sf_create ();
   6:  
   7: /**
   8:  * 释放shell_folder对象指针。
   9:  * @param sf 要释放的shell_folder对象指针。
  10:  */
  11: void sf_free (struct shell_folder* sf);
  12:  
  13: /**
  14:  * 获得桌面的绝对路径。
  15:  * @param sf shell_folder对象指针。
  16:  * @param hWnd 进行查询的窗体句柄。
  17:  * @apram ppifq 接收桌面的PIDL。
  18:  * @return 成功返回TRUE。
  19:  */
  20: BOOL sf_get_desktop_pidl (struct shell_folder* sf, HWND hWnd, LPITEMIDLIST* ppifq);
  21:  
  22: /**
  23:  * 释放PIDL对象。
  24:  * @param sf shell_folder对象指针。
  25:  * @param lpifq 要释放的PIDL。
  26:  */
  27: void sf_free_pidl (struct shell_folder* sf, LPITEMIDLIST lpifq);
  28:  
  29: /**
  30:  * 释放枚举数据对象。
  31:  * @param sf shell_folder对象指针。
  32:  * @param ed 要释放的sf_enum_data对象指针。
  33:  */
  34: void sf_free_enum_data (struct shell_folder* sf, struct sf_enum_data* ed);
  35:  
  36: /**
  37:  * 枚举一个文件夹中的所有对象。
  38:  * @param sf shell_folder结构对象指针。
  39:  * @param hWnd 要显示提示信息的窗体句柄。
  40:  * @param pSF 要遍历的目录对象的IShellFolder接口指针。
  41:  * @param lpifq 父目录的绝对路径。
  42:  * @param grfFlags 遍历使用的标志参考IShellFolder::EnumObjects。
  43:  * @param func 遍历到一个对象调用的回调函数指针。
  44:  * 如果回调函数返回TRUE则删除sf_enum_data对象指针。
  45:  * @param args 传递给回调函数的参数。
  46:  * @return 遍历成功返回TRUE,失败返回FALSE。
  47:  */
  48: BOOL sf_enum_folder (struct shell_folder* sf, 
  49:     HWND hWnd, 
  50:     IShellFolder* pSF, 
  51:     LPITEMIDLIST lpifq, 
  52:     SHCONTF grfFlags, 
  53:     sf_cb_enum_func func, 
  54:     void* args);
  55:  
  56: /**
  57:  * 获得某个绝对PIDL的名称。
  58:  * @param sf shell_folder对象指针。
  59:  * @param pSF 父级文件夹的IShellFolder接口指针。
  60:  * @param uFlags 参考IShellFolder::GetDisplayNameOf。
  61:  * @param lpi 相对于pSF的PIDL。
  62:  * @param szName 接收文件名。
  63:  * @return 成功返回FALSE,失败返回FALSE。
  64:  */
  65: BOOL sf_get_displayname (struct shell_folder* sf, IShellFolder* pSF, DWORD uFlags, LPITEMIDLIST lpi, TCHAR szName[MAX_PATH]);
  66:  
  67: /**
  68:  * 获得某个绝对PIDL的系统小图标和选择图标。
  69:  * @param sf shell_folder对象指针。
  70:  * @param lpifq 完整路径的PIDL。
  71:  * @param iSmallIcon 接收小图标索引。
  72:  * @param iSelIcon 接收选择图标索引。
  73:  * @return 成功返回TRUE,失败返回FALSE。
  74:  */
  75: BOOL sf_get_icon (struct shell_folder* sf, LPITEMIDLIST lpifq, int* iSmallIcon, int* iSelIcon);
  76:  
  77: /**
  78:  * 获得某个绝对PIDL是否是文件夹,是否有子文件夹。
  79:  * @param sf shell_folder对象指针。
  80:  * @param pSF 父级文件夹的IShellFolder接口指针。
  81:  * @param lpi 相对于pSFP的IDL。
  82:  * @param pbIsFolder 是否是文件夹。
  83:  * @param pbHasChild 是否有子文件夹。
  84:  * @return 成功返回TRUE。
  85:  */
  86: BOOL sf_has_child (struct shell_folder* sf, IShellFolder* pSF, LPITEMIDLIST lpi, LPBOOL pbIsFolder, LPBOOL pbHasChild);

我们逐一的讲解一下实现吧:

struct shell_folder* sf_create ();

这个方法比较简单,就是创建一个shell_folder结构对象。用到了上面提到的SHGetDesktopFolder函数。

   1: struct shell_folder* 
   2: sf_create ()
   3: {
   4:     HRESULT hr;
   5:     struct shell_folder* sf = NULL;
   6:     sf = (struct shell_folder*) malloc (sizeof (struct shell_folder));
   7:     if (NULL == sf)
   8:     {
   9:         return sf;
  10:     }
  11:     ZeroMemory (sf, sizeof (struct shell_folder));
  12:  
  13:     hr = SHGetDesktopFolder (&(sf->pDesktop));
  14:     if (FAILED (hr))
  15:     {
  16:         free (sf);
  17:         return NULL;
  18:     }
  19:  
  20:     hr = SHGetMalloc (&(sf->pIMalloc));
  21:     if (FAILED (hr))
  22:     {
  23:         sf->pDesktop->lpVtbl->Release (sf->pDesktop);
  24:         free (sf);
  25:         return NULL;
  26:     }
  27:  
  28:     return sf;
  29: }
void sf_free (struct shell_folder* sf);

释放一个shell_folder结构对象指针使用的所有内存。同时要释放“桌面”的IShellFolder接口指针和IMalloc接口指针。

   1: void 
   2: sf_free (struct shell_folder* sf)
   3: {
   4:     if (sf)
   5:     {
   6:         if (sf->pDesktop)
   7:         {
   8:             sf->pDesktop->lpVtbl->Release (sf->pDesktop);
   9:         }
  10:         if (sf->pIMalloc)
  11:         {
  12:             sf->pIMalloc->lpVtbl->Release (sf->pIMalloc);
  13:         }
  14:         free (sf);
  15:     }
  16: }
BOOL sf_get_desktop_pidl (struct shell_folder* sf, HWND hWnd, LPITEMIDLIST* ppifq);

获得桌面的PIDL。调用了SHGetSpecialFolderLocation函数,这个函数可以获得一些特殊文件夹的PIDL。

   1: BOOL 
   2: sf_get_desktop_pidl (struct shell_folder* sf, HWND hWnd, LPITEMIDLIST* ppifq)
   3: {
   4:     HRESULT hr;
   5:     if (NULL == sf || NULL == ppifq)
   6:     {
   7:         return FALSE;
   8:     }
   9:  
  10:     hr = SHGetSpecialFolderLocation (hWnd, CSIDL_DESKTOP, ppifq);
  11:     if (FAILED (hr))
  12:     {
  13:         return FALSE;
  14:     }
  15:     return TRUE;
  16: }
void sf_free_pidl (struct shell_folder* sf, LPITEMIDLIST lpifq);

上面提到了,系统给出的PIDL都是ITEMIDLIST数组,系统负责分配内存,使用后需要调用IMalloc::Free来释放这些内存。

   1: void 
   2: sf_free_pidl (struct shell_folder* sf, LPITEMIDLIST lpifq)
   3: {
   4:     if (NULL == sf || NULL == lpifq)
   5:     {
   6:         return ;
   7:     }
   8:  
   9:     sf->pIMalloc->lpVtbl->Free (sf->pIMalloc, lpifq);
  10: }
void sf_free_enum_data (struct shell_folder* sf, struct sf_enum_data* ed);

枚举某个文件夹的的文件对象和子文件夹对象时,传递给回调函数的sf_enum_data结构需要使用IMalloc::Free方法来释放其全部资源。

   1: void 
   2: sf_free_enum_data (struct shell_folder* sf, struct sf_enum_data* ed)
   3: {
   4:     if (NULL == sf || NULL == ed)
   5:     {
   6:         return;
   7:     }
   8:  
   9:     if (ed->lpsfParent)
  10:     {
  11:         ed->lpsfParent->lpVtbl->Release (ed->lpsfParent);
  12:     }
  13:     if (ed->lpi)
  14:     {
  15:         sf->pIMalloc->lpVtbl->Free (sf->pIMalloc, ed->lpi);
  16:     }
  17:     if (ed->lpifq)
  18:     {
  19:         sf->pIMalloc->lpVtbl->Free (sf->pIMalloc, ed->lpifq);
  20:     }
  21:     sf->pIMalloc->lpVtbl->Free (sf->pIMalloc, ed);
  22: }
BOOL sf_enum_folder (struct shell_folder* sf, HWND hWnd, IShellFolder* pSF, LPITEMIDLIST lpifq, SHCONTF grfFlags,  sf_cb_enum_func func,  void* args);

枚举一个文件夹的所有文件对象和子文件夹对象。 
      func是回调函数,当枚举到一个对象,就调用一次这个函数。 
      args用户参数,调用func时,这个参数会传递给func。

这个方法首先调用IShellFolder接口对象pSF的EnumObjects方法获得IEnumIDList接口对象指针。 
      调用IEnumIDList接口对象的Next方法遍历,并获得每个子对象的相对PIDL。 
      将pSF的PIDL与子对象的相对PIDL相连接获得子对象的全PIDL。 
      使用相对父目录pSF的PIDL、全PIDL和父目录IShellFolder接口指针pSF构造sf_enum_data结构对象,并传递给回调函数。 
      根据回调函数的返回值判断是否释放构造的sf_enum_data结构对象指针。 
      最后别忘记释放IEnumIDList接口的指针。

   1: BOOL 
   2: sf_enum_folder (struct shell_folder* sf, HWND hWnd, IShellFolder* pSF, LPITEMIDLIST lpifq, 
   3:         SHCONTF grfFlags, sf_cb_enum_func func, void* args)
   4: {
   5:     HRESULT hr;
   6:     LPENUMIDLIST lpEnum = NULL;
   7:     LPITEMIDLIST lpi = NULL, lpifqThisItem = NULL, lpiTemp = NULL;
   8:     ULONG ulwork = 0;
   9:     struct sf_enum_data* sf_ed = NULL;
  10:  
  11:     if (NULL == sf || NULL == func)
  12:     {
  13:         return FALSE;
  14:     }
  15:     if (NULL == pSF || NULL == lpifq)
  16:     {
  17:         // 当作桌面处理
  18:         pSF = sf->pDesktop;
  19:     }
  20:     pSF->lpVtbl->AddRef (pSF);
  21:  
  22:     hr = pSF->lpVtbl->EnumObjects (pSF, hWnd, grfFlags, 
  23:         & lpEnum);
  24:     if (FAILED (hr))
  25:     {
  26:         goto GOERROR;
  27:     }
  28:  
  29:     while (S_OK == lpEnum->lpVtbl->Next(lpEnum, 1, &lpi, &ulwork) && ulwork > 0)
  30:     {
  31:         sf_ed = (struct sf_enum_data*) sf->pIMalloc->lpVtbl->Alloc(sf->pIMalloc, sizeof(struct sf_enum_data));
  32:         if (NULL == sf_ed)
  33:         {
  34:             goto GOERROR;
  35:         }
  36:         ZeroMemory (sf_ed, sizeof (struct sf_enum_data));
  37:  
  38:         lpifqThisItem = pidl_concat(sf->pIMalloc, lpifq, lpi);
  39:         if (NULL == lpifqThisItem)
  40:         {
  41:             goto GOERROR;
  42:         }
  43:  
  44:         sf_ed->lpifq = lpifqThisItem;
  45:         sf_ed->lpi = lpi;
  46:         sf_ed->lpsfParent = pSF;
  47:  
  48:         if (func (sf, sf_ed, args))
  49:         {
  50:             if (sf_ed)
  51:             {
  52:                 if (sf_ed->lpi)
  53:                 {
  54:                     sf->pIMalloc->lpVtbl->Free (sf->pIMalloc, sf_ed->lpi);
  55:                 }
  56:                 if (sf_ed->lpifq)
  57:                 {
  58:                     sf->pIMalloc->lpVtbl->Free (sf->pIMalloc, sf_ed->lpifq);
  59:                 }
  60:                 sf->pIMalloc->lpVtbl->Free (sf->pIMalloc, sf_ed);
  61:             }
  62:         }
  63:     }
  64:     if (pSF)
  65:     {
  66:         pSF->lpVtbl->Release (pSF);
  67:     }
  68:     if (lpEnum)
  69:     {
  70:         lpEnum->lpVtbl->Release (lpEnum);
  71:     }
  72:     return TRUE;
  73: GOERROR:
  74:     if (pSF)
  75:     {
  76:         pSF->lpVtbl->Release (pSF);
  77:     }
  78:     if (lpEnum)
  79:     {
  80:         lpEnum->lpVtbl->Release (lpEnum);
  81:     }
  82:     if (sf_ed)
  83:     {
  84:         if (sf_ed->lpi)
  85:         {
  86:             sf->pIMalloc->lpVtbl->Free (sf->pIMalloc, sf_ed->lpi);
  87:         }
  88:         if (sf_ed->lpifq)
  89:         {
  90:             sf->pIMalloc->lpVtbl->Free (sf->pIMalloc, sf_ed->lpifq);
  91:         }
  92:         sf->pIMalloc->lpVtbl->Free (sf->pIMalloc, sf_ed);
  93:     }
  94:     return FALSE;
  95: }
BOOL sf_get_displayname (struct shell_folder* sf, IShellFolder* pSF, DWORD uFlags, LPITEMIDLIST lpi, TCHAR szName[MAX_PATH]);

使用某个文件夹的IShellFolder,根据文件或子文件夹的相对PIDL,获得文件或子文件夹的显示名称。使用了上面提到的IShellFolder的GetDisplayNameOf方法获得显示名称,再调用StrRetToBuf将STRRET转换为字符串。

   1: BOOL 
   2: sf_get_displayname (struct shell_folder* sf, IShellFolder* pSF, DWORD uFlags, LPITEMIDLIST lpi, TCHAR szName[MAX_PATH])
   3: {
   4:     STRRET str;
   5:     HRESULT hr;
   6:     if (NULL == sf)
   7:     {
   8:         return FALSE;
   9:     }
  10:  
  11:     if (NULL == pSF)
  12:     {
  13:         pSF = sf->pDesktop;
  14:     }
  15:  
  16:     hr = pSF->lpVtbl->GetDisplayNameOf (pSF, lpi, uFlags, &str);
  17:     if (FAILED (hr))
  18:     {
  19:         return FALSE;
  20:     }
  21:  
  22:     hr = StrRetToBuf (&str, lpi, szName, MAX_PATH);
  23:     if (FAILED (hr))
  24:     {
  25:         return FALSE;
  26:     }
  27:     return TRUE;
  28: }
BOOL sf_get_icon (struct shell_folder* sf, LPITEMIDLIST lpifq, int* iSmallIcon, int* iSelIcon);

通过这个函数可以获得某个对象的图标,以及选中时候的图标。例如在TreeView中,当选中某个文件夹时,某个文件夹的图标就会变成打开的样子。这里获得的是图标的索引。图标存储在系统的ImageList里面,后面的TreeView例子会详细介绍。我们通过SHGetFileInfo函数获取图标信息。

   1: BOOL 
   2: sf_get_icon (struct shell_folder* sf, LPITEMIDLIST lpifq, int* iSmallIcon, int* iSelIcon)
   3: {
   4:     SHFILEINFO sfi;
   5:  
   6:     if (NULL == sf || NULL == lpifq || NULL == iSmallIcon || NULL == iSelIcon)
   7:     {
   8:         return FALSE;
   9:     }
  10:  
  11:     // 获取小图标
  12:     ZeroMemory (&sfi, sizeof (SHFILEINFO));
  13:     SHGetFileInfo ((LPCSTR)lpifq, 0, &sfi, sizeof (SHFILEINFO), SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
  14:     *iSmallIcon = sfi.iIcon;
  15:  
  16:     // 获取选择图标
  17:     ZeroMemory (&sfi, sizeof (SHFILEINFO));
  18:     SHGetFileInfo ((LPCSTR)lpifq, 0, &sfi, sizeof (SHFILEINFO), SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_OPENICON);
  19:     *iSelIcon = sfi.iIcon;
  20:  
  21:     return TRUE;
  22: }
BOOL sf_has_child (struct shell_folder* sf, IShellFolder* pSF, LPITEMIDLIST lpi, LPBOOL pbIsFolder, LPBOOL pbHasChild);

这个函数用于判断某个对象是否是文件夹,这个文件夹是否有子文件夹。在TreeView中只显示文件夹,并且如果某个文件夹有子文件夹那么这个文件夹节点应该可以被继续展开。我们通过上面介绍的IShellFolder的GetAttributesOf方法实现该功能。

   1: BOOL 
   2: sf_has_child (struct shell_folder* sf, IShellFolder* pSF, LPITEMIDLIST lpi, LPBOOL pbIsFolder, LPBOOL pbHasChild)
   3: {
   4:     ULONG ulAttrs = SFGAO_HASSUBFOLDER | SFGAO_FOLDER;
   5:     if (NULL == sf || NULL == pbIsFolder || NULL == pbHasChild)
   6:     {
   7:         return FALSE;
   8:     }
   9:     
  10:     *pbIsFolder = FALSE;
  11:     *pbHasChild = FALSE;
  12:  
  13:     if (NULL == pSF)
  14:     {
  15:         pSF = sf->pDesktop;
  16:     }
  17:  
  18:     pSF->lpVtbl->GetAttributesOf (pSF, 1, &lpi, &ulAttrs);
  19:     if (ulAttrs & SFGAO_FOLDER)
  20:     {
  21:         *pbIsFolder = TRUE;
  22:     }
  23:  
  24:     if (ulAttrs & SFGAO_HASSUBFOLDER)
  25:     {
  26:         *pbHasChild = TRUE;
  27:     }
  28:  
  29:     return TRUE;
  30: }