在開發輔助工具時,常需要模擬 Windows 檔案總管中快捷方式的「查找目標」功能——即打開目標檔案所在的資料夾,並自動選中該檔案。雖然 Windows XP 引入了 SHOpenFolderAndSelectItems API,但若需支援 Windows 2000 或 98,就必須另闢蹊徑。本文將詳細解析如何結合公開與非公開 API,打造一個跨版本相容的解決方案。
HRESULT SHOpenFolderAndSelectItems(
LPCITEMIDLIST pidlFolder,
UINT cidl,
LPCITEMIDLIST *apidl,
DWORD dwFlags
);
此函數可直接開啟資料夾並選中指定項目,但僅支援 Windows XP 及以上版本。
經過反組織與研究,發現 Shell 內部使用 SHGetIDispatchForFolder 取得資料夾的 IWebBrowserApp 介面,進而存取 IShellFolderViewDual 以操作檢視。其原型為:
HRESULT (WINAPI* gpfSHGetIDispatchForFolder)(
ITEMIDLIST* pidl,
IWebBrowserApp** ppIWebBrowserApp
);
此函數位於 SHDOCVW.DLL 中,需動態載入並透過 GetProcAddress 取得位址。
ITEMIDLIST 代表 Shell 命名空間中的項目識別碼列表。快捷方式提供的 PIDL 包含完整路徑(資料夾 + 檔案名),呼叫 SHGetIDispatchForFolder 前必須將其截斷為「僅資料夾」的 PIDL。
// 找出檔案名在 PIDL 中的偏移量
ITEMIDLIST *pidl = pidlFolder;
ITEMIDLIST *pidl2 = pidl;
ITEMIDLIST *pIdlFile = pidl;
USHORT cb;
while (cb = pIdlFile->mkid.cb) {
pidl2 = pIdlFile;
pIdlFile = (ITEMIDLIST*)((BYTE*)pIdlFile + cb);
}
// pidl2 現在指向最後一個項目(檔案名)
cb = pidl2->mkid.cb;
pidl2->mkid.cb = 0; // 暫時截斷,變成「資料夾 PIDL」
SHGetIDispatchForFolder 函數指標。IWebBrowserApp 介面。IShellFolderViewDual 介面,用於操作檢視選取項目。pidl2->mkid.cb 恢復),建立 COleVariant 包裝完整 PIDL(含檔案名)。SelectItem,旗標 0x1D (SVSI_SELECT | SVSI_FOCUSED | SVSI_ENSUREVISIBLE | SVSI_EDIT) 選中並聚焦目標檔案。完整函數 XZSHOpenFolderAndSelectItems 採用「優先嘗試系統 API,失敗則回退自行實作」的策略:
BOOL XZSHOpenFolderAndSelectItems(ITEMIDLIST *pidlFolder) {
// 1. 嘗試載入 Shell32.dll 並取得 SHOpenFolderAndSelectItems
HINSTANCE hShell32 = LoadLibrary(_T("Shell32.dll"));
auto pfn = (HRESULT(WINAPI*)(LPCITEMIDLIST*, UINT, LPCITEMIDLIST*, DWORD))
GetProcAddress(hShell32, "SHOpenFolderAndSelectItems");
if (pfn && SUCCEEDED(pfn((LPCITEMIDLIST*)&pidlFolder, 0, NULL, 0))) {
FreeLibrary(hShell32);
return TRUE; // XP+ 系統原生支援
}
FreeLibrary(hShell32);
// 2. 回退:手動實作(上述 PIDL 分離 + SHGetIDispatchForFolder 流程)
// ... 程式碼省略,詳見完整原始碼 ...
}
FindTarget 函數示範如何解析捷徑(.lnk)或直接指定檔案路徑,取得目標的 ITEMIDLIST:
void FindTarget(CString str) {
CoInitialize(NULL);
IShellLink* psl;
CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
IID_IShellLink, (LPVOID*)&psl);
psl->SetPath(str);
ITEMIDLIST* pidl;
psl->GetIDList(&pidl); // 取得目標 PIDL
// 若是捷徑檔,需載入後重新取得目標 PIDL
IPersistFile* ppf;
psl->QueryInterface(IID_IPersistFile, (void**)&ppf);
WCHAR wsz[MAX_PATH];
// ... 轉換為 Unicode 路徑 ...
ppf->Load(wsz, STGM_READ);
psl->GetIDList(&pidl); // 此時為真正目標的 PIDL
ppf->Release();
XZSHOpenFolderAndSelectItems(pidl); // 執行查找目標
psl->Release();
CoUninitialize();
}
本文附贈完整 Demo 專案,包含上述所有函數與錯誤處理,可直接整合至您的專案中。