如何通过Shell扩展获取右键文件夹背景及文件/文件夹路径
Shell扩展上下文菜单路径获取问题
需求场景
当用户打开如D:\projects的文件夹,右键点击空白背景调用上下文菜单,点击名为‘Display Path’的菜单项时,需调用控制台程序显示该文件夹路径。通过注册表为控制台程序命令添加%V参数可实现此功能,但需通过Shell扩展菜单处理程序完成。
当前问题
已编写功能正常的Shell上下文菜单DLL,但无法获取右键文件夹背景对应的路径。已知IShellExtInit::Initialize()方法的PCIDLIST_ABSOLUTE pidl参数包含该路径,但尝试将其转换为字符串时程序崩溃,导致资源管理器重启。尝试的两段崩溃代码如下:
第一段崩溃代码
HRESULT __stdcall Initialize(PCIDLIST_ABSOLUTE pidlFilder, IDataObject* pdtobj, HKEY hkeyProgID) { std::wstring s = L"null"; // check msg, this msgbox is shown as expected MessageBox(NULL, L"Before", L"Initialize()", MB_OK); //have problem in this line, I guess SHGetPathFromIDList((LPCITEMIDLIST) pidlFilder, (PWSTR) &s); // check msg, sometimes this msgbox is also shown as expected MessageBox(NULL, L"After", L"Initialize()", MB_OK); // but this msgbox is never shown. I removed it but code still crashes MessageBox(NULL, std::wstring(s).c_str(), L"Initialize()", MB_OK); return S_OK; }
第二段崩溃代码
IShellFolder *sf = NULL; STRRET pName = {}; sf->GetDisplayNameOf(pidlFilder, SHGDN_FORPARSING, &pName); wchar_t *d = new wchar_t; lstrcpyW(d,L"nulld"); size_t inst = MAX_PATH, outst ; mbstowcs_s(&outst, d, inst, pName.cStr, MAX_PATH); s = std::wstring(d); MessageBox(NULL, std::wstring(s).c_str(), L"Initialize()", MB_OK);
求助问题
- 上述代码崩溃的原因及解决方案,如何通过Shell扩展获取右键文件夹背景的路径?
- 如何通过Shell扩展获取右键点击文件/文件夹时的路径?
问题解答
1. 崩溃原因分析与文件夹背景路径获取方案
第一段代码崩溃原因
SHGetPathFromIDList的第二个参数需要指向已分配足够内存的缓冲区,直接把std::wstring的地址强转成PWSTR传入完全错误。std::wstring内部存储是动态的,这种操作会导致函数写入非法内存,触发崩溃。
第二段代码崩溃原因
IShellFolder *sf = NULL;后直接调用sf->GetDisplayNameOf,空指针调用成员函数必然崩溃;wchar_t *d = new wchar_t;仅分配单个宽字符内存,后续写入超过1个字符的内容会越界,导致内存损坏;STRRET的cStr仅在类型为STRRET_CSTR时有效,直接访问存在类型不匹配风险。
正确实现代码
方式一:使用SHGetPathFromIDListW
HRESULT __stdcall Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY hkeyProgID) { wchar_t szPath[MAX_PATH] = {0}; if (SHGetPathFromIDListW(pidlFolder, szPath)) { MessageBoxW(NULL, szPath, L"Folder Path", MB_OK); // 可将szPath保存到类成员变量,供后续菜单点击逻辑使用 } else { MessageBoxW(NULL, L"Failed to get path", L"Error", MB_OK); } return S_OK; }
要点:使用宽字符版本SHGetPathFromIDListW避免编码问题,缓冲区分配MAX_PATH大小确保足够存储路径。
方式二:通过IShellFolder接口获取
HRESULT __stdcall Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY hkeyProgID) { IShellFolder* pDesktopFolder = NULL; HRESULT hr = SHGetDesktopFolder(&pDesktopFolder); if (SUCCEEDED(hr)) { STRRET strRet; hr = pDesktopFolder->GetDisplayNameOf(pidlFolder, SHGDN_FORPARSING, &strRet); if (SUCCEEDED(hr)) { wchar_t szPath[MAX_PATH] = {0}; hr = StrRetToBufW(&strRet, pidlFolder, szPath, MAX_PATH); if (SUCCEEDED(hr)) { MessageBoxW(NULL, szPath, L"Folder Path", MB_OK); } StrRetRelease(&strRet); } pDesktopFolder->Release(); } return S_OK; }
要点:
- 必须通过
SHGetDesktopFolder获取桌面IShellFolder接口,不可使用空指针; - 用
StrRetToBufW安全转换STRRET到缓冲区,避免直接访问成员的类型风险; - 及时释放接口和
STRRET资源。
2. 获取右键点击文件/文件夹的路径
右键点击文件或文件夹时,Initialize的IDataObject* pdtobj参数会包含选中项信息,需提取CF_HDROP格式数据:
HRESULT __stdcall Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY hkeyProgID) { if (pdtobj != NULL) { FORMATETC fe = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; STGMEDIUM stm; HRESULT hr = pdtobj->GetData(&fe, &stm); if (SUCCEEDED(hr)) { HDROP hDrop = static_cast<HDROP>(GlobalLock(stm.hGlobal)); if (hDrop != NULL) { UINT nFiles = DragQueryFileW(hDrop, 0xFFFFFFFF, NULL, 0); for (UINT i = 0; i < nFiles; ++i) { wchar_t szFilePath[MAX_PATH] = {0}; if (DragQueryFileW(hDrop, i, szFilePath, MAX_PATH)) { MessageBoxW(NULL, szFilePath, L"File/Folder Path", MB_OK); // 保存路径到成员变量供后续使用 } } GlobalUnlock(stm.hGlobal); } ReleaseStgMedium(&stm); } } return S_OK; }
说明:
CF_HDROP是Shell传递选中文件/文件夹路径的标准格式;DragQueryFileW第一个参数传0xFFFFFFFF可获取选中项数量,循环获取每个路径;- 用完
STGMEDIUM必须调用ReleaseStgMedium释放资源。
内容的提问来源于stack exchange,提问作者JC JC
相关产品推荐
相关产品推荐

