其實有很多種方法實現遍歷指定路徑下的文件,最普通的可能就是用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);
}