如何从ITEMIDLIST(pidl)快速获取文件大小及修改日期?
问题
我看到许多实现资源管理器样式视图的示例,这类实现的核心问题是速度,瓶颈在于文件大小的获取——当前需额外调用接口获取文件状态(修改日期与文件大小),代码实现如下:
case static_cast<int>(Columns::SizeColumn): //deliberate fallthrough case static_cast<int>(Columns::DateModifiedColumn): { CString sPath; CFileStatus fs; if (pItem->m_pidlFQ.ToPath(sPath) && CFile::GetStatus(sPath, fs)) { CString sText; if (nColumn == static_cast<int>(Columns::SizeColumn)) { if ((fs.m_attribute & (CFile::directory | CFile::volume)) == 0) OnFormatFileSize(fs.m_size, sText); } else OnFormatFileDate(fs.m_mtime, sText); return sText; } break; }
但这种方式速度较慢,是否存在更简便的方法直接从pidl获取文件修改日期与大小?
回答
当然有更高效的方案,无需将PIDL转换为路径再调用CFile::GetStatus,直接通过Shell原生接口从PIDL提取所需信息,能大幅提升性能:
方法1:使用SHGetFileInfo API
这是Win32原生的Shell API,支持直接从PIDL获取文件大小、修改日期等属性,跳过路径转换步骤:
case static_cast<int>(Columns::SizeColumn): //deliberate fallthrough case static_cast<int>(Columns::DateModifiedColumn): { SHFILEINFO sfi = {0}; UINT flags = SHGFI_PIDL; // 指定输入为PIDL if (nColumn == static_cast<int>(Columns::SizeColumn)) { flags |= SHGFI_SIZESIZE; // 请求文件大小 } else { flags |= SHGFI_ATTRIBUTES; // 先获取属性判断类型 } if (SHGetFileInfo((LPCVOID)pItem->m_pidlFQ, 0, &sfi, sizeof(sfi), flags)) { CString sText; if (nColumn == static_cast<int>(Columns::SizeColumn)) { // 组合高低位得到64位文件大小 ULONGLONG fileSize = (static_cast<ULONGLONG>(sfi.nFileSizeHigh) << 32) | sfi.nFileSizeLow; // 排除目录和卷 if (!(sfi.dwAttributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_VOLUME))) OnFormatFileSize(fileSize, sText); } else { // 通过IShellFolder获取修改日期 IShellFolder* pParentFolder = nullptr; LPITEMIDLIST pidlRelative = nullptr; if (SUCCEEDED(SHBindToParent(pItem->m_pidlFQ, IID_IShellFolder, (void**)&pParentFolder, &pidlRelative))) { FILETIME ftModified = {0}; if (SUCCEEDED(pParentFolder->GetDetailsOf(pidlRelative, SHCOL_INDEX_DATE_MODIFIED, &ftModified))) { SYSTEMTIME stLocal; FileTimeToSystemTime(&ftModified, &stLocal); OnFormatFileDate(stLocal, sText); } pParentFolder->Release(); CoTaskMemFree(pidlRelative); } } return sText; } break; }
方法2:使用现代IShellItem2接口
这是Windows Vista及以后推荐的Shell接口,支持通过属性键直接获取文件元数据,代码更简洁且性能更优:
case static_cast<int>(Columns::SizeColumn): //deliberate fallthrough case static_cast<int>(Columns::DateModifiedColumn): { IShellItem2* pShellItem = nullptr; if (SUCCEEDED(SHCreateItemFromIDList(pItem->m_pidlFQ, IID_IShellItem2, (void**)&pShellItem))) { CString sText; if (nColumn == static_cast<int>(Columns::SizeColumn)) { ULONGLONG fileSize = 0; DWORD dwAttrs = 0; // 获取文件大小和属性 if (SUCCEEDED(pShellItem->GetFileAttributesAndSize(&dwAttrs, &fileSize))) { // 排除目录和卷 if (!(dwAttrs & (SFGAO_FOLDER | SFGAO_VOLUME))) OnFormatFileSize(fileSize, sText); } } else { PROPVARIANT pvDate = {0}; if (SUCCEEDED(pShellItem->GetValue(PKEY_DateModified, &pvDate))) { SYSTEMTIME stLocal; FileTimeToSystemTime(&pvDate.filetime, &stLocal); OnFormatFileDate(stLocal, sText); PropVariantClear(&pvDate); } } pShellItem->Release(); return sText; } break; }
性能提升原因
- 跳过PIDL转文件路径的额外开销,直接操作Shell内部数据结构
- 利用Shell缓存的元数据,避免重复访问文件系统
- 减少系统调用次数,批量获取属性时优势更明显
内容的提问来源于stack exchange,提问作者xMRi
相关产品推荐
相关产品推荐

