其实有很多种方法实现遍历指定路径下的文件,最普通的可能就是用FindFirstFile、FindNextFile等API来实现,这种实现方法也可以,但是,如果文件夹时里面有子文件夹的话,用这个方法实现起来就有点麻烦,可能要用递归,而递归这种方式效率是个很大的问题,在这不打算讲这种方法,下面要说的是另一种方式------Windows Shell。
其实用Shell来实现,思路很简单,先通过某种方式得到对应路径的PIDL,然后再得到与之对应的IShellFolder对象,再用IShellFolder::EnumObjects得到IEnumIDList接口,最终用IEnumIDList接口来遍历所有的文件。思路是这样子的,但这里面还是涉及到了很多关于Shell的概念,在看下面这段代码之前,需要搞明白,不然看起来可能很费劲。
BOOL GetAllFilesFromFolderPath(IN LPCTSTR lpFolderPath, OUT vector<wstring> &vctFiles) { if ( !PathFileExists(lpFolderPath) ) { return FALSE; } WCHAR szFolderPath[MAX_PATH] = { 0 }; IShellFolder *psfDesktop = NULL; IShellFolder *psfWorkDir = NULL; IEnumIDList *penumIDList = NULL; LPITEMIDLIST pidworkDir = NULL; wcscpy_s(szFolderPath, MAX_PATH, lpFolderPath); // +1 HRESULT hr = SHGetDesktopFolder(&psfDesktop); if (SUCCEEDED(hr)) { // +2 hr = psfDesktop->ParseDisplayName(NULL, NULL, szFolderPath, NULL, &pidworkDir, NULL); } if (SUCCEEDED(hr)) { // +3 hr = psfDesktop->BindToObject(pidworkDir, NULL, IID_PPV_ARGS(&psfWorkDir)); } if (SUCCEEDED(hr)) { // +4 hr = psfWorkDir->EnumObjects(NULL, SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN, &penumIDList); } if (SUCCEEDED(hr)) { ULONG celtFetched = 0; LPITEMIDLIST pidChild = NULL; while (SUCCEEDED(penumIDList->Next(1, &pidChild, &celtFetched)) && (1 == celtFetched)) { // Get the file path from the PIDL of the item. LPITEMIDLIST pRealIDL = NULL; HRESULT hr = SHGetRealIDL(psfWorkDir, pidChild, &pRealIDL); if (SUCCEEDED(hr)) { STRRET strName; hr = psfWorkDir->GetDisplayNameOf(pRealIDL, SHGDN_FORPARSING, &strName); if (SUCCEEDED(hr)) { WCHAR szName[MAX_PATH] = { 0 }; hr = StrRetToBuf(&strName, pRealIDL, szName, MAX_PATH); if (SUCCEEDED(hr)) { vctFiles.push_back(szName); } } CoTaskMemFree(pRealIDL); } CoTaskMemFree(pidChild); } } // -4 SAFE_RELEASE(penumIDList); // -3 SAFE_RELEASE(psfWorkDir); // -2 CoTaskMemFree(pidworkDir); // -1 SAFE_RELEASE(psfDesktop); return (vctFiles.size() > 0); }
对上面的程序简单说明一下:
// +1 那句,首先得到桌面所对就的IShellFolder对象,可以通过它得到指定路径的PIDL。
// +2 那句,调用ParseDisplayName方法来得到指定路径的PIDL,注意,该方法第三个参数类型是LPWSTR,不是LPCWSTR,这意味着它内部可能会改这个参数。
// +3 那句,通过ShellFolder Bind 一下,得到一个新的ShellFolder,传入一个PIDL,这个PIDL就是第二步得到来的。
// +4 那句,枚举出IEnumIDList接口对象,通过调用它的Next方法,就能取出每一个子元素的PIDL,再通过这个PIDL来得到其对应的路径。
// 最后别忘记释放COM接口,不然就会有内存泄漏。
下面这一段代码就是根据一个PIDL得到其路径,当然,这也可以写成一个函数,调用方便。
LPITEMIDLIST pRealIDL = NULL; HRESULT hr = SHGetRealIDL(psfWorkDir, pidChild, &pRealIDL); if (SUCCEEDED(hr)) { STRRET strName; hr = psfWorkDir->GetDisplayNameOf(pRealIDL, SHGDN_FORPARSING, &strName); if (SUCCEEDED(hr)) { WCHAR szName[MAX_PATH] = { 0 }; hr = StrRetToBuf(&strName, pRealIDL, szName, MAX_PATH); if (SUCCEEDED(hr)) { vctFiles.push_back(szName); } } CoTaskMemFree(pRealIDL); }