使用Windows Shell API枚举指定文件夹文件时路径异常问题排查
问题与修复方案
问题现象
使用Windows Shell API枚举C:\Users\Lilo\Documents路径下的文件时,调用pItem->GetDisplayName(SIGDN_FILESYSPATH, &displayName)返回的路径却是C:\Users\Lilo\Desktop\file.txt(该文件实际位于Documents文件夹),后续调用SHGetPropertyStoreFromIDList或SHGetPropertyStoreFromParsingName均失败,错误码为HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)。
根本原因
从IEnumIDList枚举得到的ITEMIDLIST* pidl是相对于父文件夹的相对PIDL,而SHCreateItemFromIDList默认要求传入绝对PIDL(从桌面开始的完整PIDL)才能正确解析文件路径。直接用相对PIDL创建IShellItem会导致Shell错误解析路径,指向无关位置的文件。
修复方案
- 使用
SHCreateItemWithParent替代SHCreateItemFromIDList,该方法可以基于父文件夹的IShellItem(或PIDL)和子项的相对PIDL,正确创建子项的IShellItem,避免路径解析错误。 - 获取属性存储时,优先通过正确创建的子IShellItem调用
GetPropertyStore方法,比直接用PIDL调用SHGetPropertyStoreFromIDList更可靠。
修改后的完整代码
#include <windows.h> #include <iostream> #include <shlobj.h> #include <atlcomcli.h> #include <propkey.h> inline void enumFiles(const std::wstring& folderPath) { HRESULT hr = CoInitialize(NULL); if (FAILED(hr)) { std::cout << "CoInitialize failed with HRESULT:" << hr << std::endl; return; } CComPtr<IShellItem> pFolderItem = NULL; hr = SHCreateItemFromParsingName(folderPath.c_str(), NULL, IID_PPV_ARGS(&pFolderItem)); if (FAILED(hr)) { std::cout << "SHCreateItemFromParsingName failed with HRESULT:" << hr << std::endl; CoUninitialize(); return; } CComPtr<IShellFolder> pFolder = NULL; hr = pFolderItem->BindToHandler(NULL, BHID_SFObject, IID_PPV_ARGS(&pFolder)); if (FAILED(hr)) { std::cout << "BindToHandler failed with HRESULT:" << hr << std::endl; CoUninitialize(); return; } CComPtr<IEnumIDList> pEnum = NULL; hr = pFolder->EnumObjects(NULL, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &pEnum); if (FAILED(hr)) { std::cout << "EnumObjects failed with HRESULT:" << hr << std::endl; CoUninitialize(); return; } ITEMIDLIST* pidlRel = NULL; ULONG fetched; while (pEnum->Next(1, &pidlRel, &fetched) == S_OK) { // 使用SHCreateItemWithParent基于父文件夹创建子项IShellItem(处理相对PIDL) CComPtr<IShellItem> pItem; hr = SHCreateItemWithParent(NULL, pFolderItem, pidlRel, IID_PPV_ARGS(&pItem)); if (FAILED(hr)) { std::cout << "SHCreateItemWithParent failed with HRESULT:" << hr << std::endl; CoTaskMemFree(pidlRel); continue; } // 获取正确的文件系统路径 LPWSTR displayName = NULL; hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &displayName); if (SUCCEEDED(hr)) { std::wcout << L"File path: " << displayName << std::endl; CoTaskMemFree(displayName); } else { std::cout << "GetDisplayName failed with HRESULT:" << hr << std::endl; } // 通过子IShellItem获取属性存储 CComPtr<IPropertyStore> pPropertyStore; hr = pItem->GetPropertyStore(GPS_DEFAULT, IID_PPV_ARGS(&pPropertyStore)); if (SUCCEEDED(hr)) { // 示例:获取文件大小属性 PROPVARIANT propVar; PropVariantInit(&propVar); hr = pPropertyStore->GetValue(PKEY_Size, &propVar); if (SUCCEEDED(hr)) { if (propVar.vt == VT_UI8) { std::cout << "File size: " << propVar.uhVal.QuadPart << " bytes" << std::endl; } PropVariantClear(&propVar); } } else { std::cout << "GetPropertyStore failed with HRESULT:" << hr << std::endl; } CoTaskMemFree(pidlRel); } CoUninitialize(); } int main(int argc, char* argv[]) { enumFiles(L"C:\\Users\\Lilo\\Documents"); return 0; }
额外说明
SHCreateItemWithParent是处理相对PIDL的标准方式,能确保Shell正确解析子项的位置。- 直接通过
IShellItem::GetPropertyStore获取属性存储,比单独调用SHGetPropertyStoreFromIDList更简洁且不易出错,因为IShellItem已经包含了正确的项信息。 - 注意所有通过Shell API分配的内存(如PIDL、displayName)都需要用
CoTaskMemFree释放,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Lillo
相关产品推荐
相关产品推荐

