How to use SHCreateItemFromParsingName with names from wrapper namespace?
I use SHCreateItemFromParsingName
to turn the path into IShellItem
:
IShellItem ParseName(String path)
{
IShellItem shellItem;
HRESULT hr = SHCreateItemFromParsingName(path, null, IShellItem, out shellItem);
if (Failed(hr))
throw new ECOMException(hr);
return shellItem;
}
Note : A
IShellItem
was introduced around 2006 to provide a nice wrapper aroundIShellFolder
+ constructspidl
under Windows 95. You can even ask toIShellItem
flip it underIShellFolder
andpidl
using the interface and methodIParentAndItem.GetParentAndItem
.
Different things have different display names
I can get some known locations in the shell namespace and see their absolute parsing ( SIGDN_DESKTOPABSOLUTEPARSING
) and edit ( SIGDN_DESKTOPABSOLUTEEDITING
) the displayed names:
| Path | Editing | Parsing |
|-------------------|-----------------------|-------------------------------------------------------------------|
| C:\ | "C:\" | "C:\" |
| C:\Windows | "C:\Windows" | "C:\Windows" |
| Desktop | "Desktop" | "C:\Users\Ian\Desktop" |
| Computer | "This PC" | "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}" |
| Recycle Bin | "Recycle Bin" | "::{645FF040-5081-101B-9F08-00AA002F954E}" |
| Documents Library | "Libraries\Documents" | "::{031E4825-7B94-4DC3-B131-E946B44C8DD5}\Documents.library-ms" " |
| Startup | "C:\Users\Ian\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup" | "C:\Users\Ian\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup" |
How to parse them when the user enters them into them?
I can use IFileOpenDialog
to let the user select one of these folders. But I would really like the user to be able to print
- "C: \ Users"
- "C: \ Windows \ Fonts"
- "This PC"
- "Basket"
- "Library"
- "Launch"
- "Fonts"
and you can analyze it on IShellItem
.
The problem is that some of the paths are not parsed SHCreateItemFromParsingName
:
-
SHCreateItemFromParsingName("C:\")
: Parsi -
SHCreateItemFromParsingName("C:\Windows")
: Parsis -
SHCreateItemFromParsingName("")
: Parsis (but becomes " This PC ") -
SHCreateItemFromParsingName("This PC")
: Fails -
SHCreateItemFromParsingName("Recycle Bin")
: Fails -
SHCreateItemFromParsingName("Libraries")
: Fails -
SHCreateItemFromParsingName("OneDrive")
: Fails -
SHCreateItemFromParsingName("Libraries\Documents")
: Fails -
SHCreateItemFromParsingName("Network")
: Fails -
SHCreateItemFromParsingName("Startup")
: Fails
Meanwhile, the IFileOpenDialog control that my program uses is fine with them:
How can I parse the various special shell name fields that the user can enter (in that the Windows Explorer dialog and the IFileOpen dialog can parse) into IShellItem
for this folder?
The real question is that I want the user to be able to have a recent MRU list that contains things like:
- C: \ Windows
- Baskets
- This PC
and you can analyze them later.
source to share
It would be interesting to debug Explorer and see how it does it.
My suggestion; if the original parsing failed, add shell:
to the path string and try expanding it again with SHParseDisplayName
. If you set STR_PARSE_SHELL_PROTOCOL_TO_FILE_OBJECTS
in the context of the bindings, you can also bind to special files. Shell protocol: allows parsing the internal / canonical name of special / known folders, but I don't know if it checks the display name as well.
Edit:
I had a chance to play around a bit now, and shell: prefix is ββnot a big improvement because it only checks for known canonical folder names:
PCWSTR paths[] = {
TEXT("C:\\"),
TEXT("C:\\Windows"),
TEXT(""),
TEXT("This PC"),
TEXT("MyComputerFolder"), // Canonical KF name
TEXT("Recycle Bin"),
TEXT("RecycleBinFolder"), // Canonical KF name
TEXT("Libraries"),
TEXT("OneDrive"),
TEXT("Libraries\\Documents"),
TEXT("Network"),
TEXT("NetworkPlacesFolder"), // Canonical KF name
TEXT("Startup"),
};
OleInitialize(0);
INT pad = 0, fill, i;
for (i = 0; i < ARRAYSIZE(paths); ++i) pad = max(pad, lstrlen(paths[i]));
for (i = 1, fill = printf("%-*s | Original | shell: |\n", pad, ""); i < fill; ++i) printf("-"); printf("\n");
for (i = 0; i < ARRAYSIZE(paths); ++i)
{
WCHAR buf[MAX_PATH], *p1 = NULL, *p2 = NULL;
IShellItem*pSI;
HRESULT hr = SHCreateItemFromParsingName(paths[i], NULL, IID_IShellItem, (void**) &pSI);
if (SUCCEEDED(hr)) pSI->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &p1), pSI->Release();
wsprintf(buf, L"shell:%s", paths[i]);
HRESULT hr2 = SHCreateItemFromParsingName(buf, NULL, IID_IShellItem, (void**) &pSI);
if (SUCCEEDED(hr2)) pSI->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &p2), pSI->Release();
wprintf(L"%-*s | %.8x | %.8x | %s\n", pad, paths[i], hr, hr2, p2 && *p2 ? p2 : p1 ? p1 : L"");
CoTaskMemFree(p1), CoTaskMemFree(p2);
}
gives me this result:
| Original | shell: |
-------------------------------------------
C:\ | 00000000 | 80070003 | C:\
C:\Windows | 00000000 | 80070003 | C:\Windows
| 00000000 | 80070003 | ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}
This PC | 80070002 | 80070003 |
MyComputerFolder | 80070002 | 00000000 | ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}
Recycle Bin | 80070002 | 80070003 |
RecycleBinFolder | 80070002 | 00000000 | ::{645FF040-5081-101B-9F08-00AA002F954E}
Libraries | 80070002 | 00000000 | ::{031E4825-7B94-4DC3-B131-E946B44C8DD5}
OneDrive | 80070002 | 80070003 |
Libraries\Documents | 80070002 | 80070002 |
Network | 80070002 | 80070003 |
NetworkPlacesFolder | 80070002 | 00000000 | ::{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}
Startup | 80070002 | 00000000 | C:\Users\Anders\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
In Windows 8 SHCreateItemFromParsingName
names are SHParseDisplayName
(with STR_PARSE_AND_CREATE_ITEM
and STR_PARSE_TRANSLATE_ALIASES
), so even Microsoft is not able to separate parsing and display names in their API.
If you want to stay away from undocumented interfaces, you will need to add a third pass where you check the names of the known folder names. Or alternatively, as Raymond Chen suggests in the comments; parse each path component manually against the item mapping names in IShellFolder
.
source to share
It would be interesting to debug Explorer and see how it does it.
start from Windows 7 shell use the following undocumented interface (until it changes from win 7 to last win 10)
MIDL_INTERFACE("88DF9332-6ADB-4604-8218-508673EF7F8A") IShellUrl : public IUnknown
{
virtual HRESULT STDMETHODCALLTYPE ParseFromOutsideSource(PCWSTR,DWORD);
virtual HRESULT STDMETHODCALLTYPE GetUrl(PWSTR,DWORD);
virtual HRESULT STDMETHODCALLTYPE SetUrl(PCWSTR,DWORD);
virtual HRESULT STDMETHODCALLTYPE GetDisplayName(PWSTR,DWORD);
virtual HRESULT STDMETHODCALLTYPE GetPidl(ITEMIDLIST_ABSOLUTE * *);
virtual HRESULT STDMETHODCALLTYPE SetPidl(ITEMIDLIST_ABSOLUTE const *);
virtual HRESULT STDMETHODCALLTYPE SetPidlAndArgs(ITEMIDLIST_ABSOLUTE const *,PCWSTR);
virtual PWSTR STDMETHODCALLTYPE GetArgs();
virtual HRESULT STDMETHODCALLTYPE AddPath(ITEMIDLIST_ABSOLUTE const *);
virtual void STDMETHODCALLTYPE SetCancelObject(ICancelMethodCalls *);
virtual HRESULT STDMETHODCALLTYPE StartAsyncPathParse(HWND,PCWSTR,DWORD,ICancelMethodCalls *);
virtual HRESULT STDMETHODCALLTYPE GetParseResult();
virtual HRESULT STDMETHODCALLTYPE SetRequestID(int);
virtual HRESULT STDMETHODCALLTYPE GetRequestID(int *);
virtual HRESULT STDMETHODCALLTYPE SetNavFlags(int,int);
virtual HRESULT STDMETHODCALLTYPE GetNavFlags(long *);
virtual HRESULT STDMETHODCALLTYPE Execute(struct IShellNavigationTarget *,int *,DWORD);
virtual HRESULT STDMETHODCALLTYPE SetCurrentWorkingDir(ITEMIDLIST_ABSOLUTE const *);
virtual void STDMETHODCALLTYPE SetMessageBoxParent(HWND);
virtual HRESULT STDMETHODCALLTYPE GetPidlNoGenerate(ITEMIDLIST_ABSOLUTE * *);
virtual DWORD STDMETHODCALLTYPE GetStandardParsingFlags(BOOL);
};
class DECLSPEC_UUID("4BEC2015-BFA1-42FA-9C0C-59431BBE880E") ShellUrl;
we can use it to display display names like Recycle Bin
, This PC
etc. (use IFileOpenDialog dialog)
we can use it synchronously or asynchronously. for synchronous call needs
ParseFromOutsideSource(L"your name", flags = GetStandardParsingFlags(0))
if this call is ok, we can get and use ITEMIDLIST_ABSOLUTE*
on call GetPidl
(when you no longer need to free it on ILFree
), also if the file system path exists, we can get it GetUrl
otherwise the original name is returned.
it is also possible to use asynchronous parsing - you need to call StartAsyncPathParse
- pass your own hwnd and optional interface ICancelMethodCalls
. when the operation completes, message the shell RegisterWindowMessage(L"AC_ParseComplete")
( wParam == IShellUrl*, lParam == 0
) to your window. you can get the final status on call GetParseResult()
and if it's ok - useGetPidl
example code for synchronous parsing
HRESULT ParsePath(PCWSTR path, IShellItem **ppsi)
{
IShellUrl* pShUrl;
HRESULT hr = CoCreateInstance(__uuidof(ShellUrl), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pShUrl));
if (hr == S_OK)
{
if (SUCCEEDED(hr = pShUrl->ParseFromOutsideSource(path, pShUrl->GetStandardParsingFlags(TRUE))))
{
ITEMIDLIST_ABSOLUTE *pidl;
if (SUCCEEDED(hr = pShUrl->GetPidl(&pidl)))
{
hr = SHCreateItemFromIDList(pidl, IID_PPV_ARGS(ppsi));
//WCHAR sz[MAX_PATH];
//if (SUCCEEDED(pShUrl->GetUrl(sz, RTL_NUMBER_OF(sz)))) DbgPrint(">%S\n", sz);
ILFree(pidl);
}
}
pShUrl->Release();
}
return hr;
}
void tt(PCWSTR path)
{
IShellItem *psi;
if (0 <= ParsePath(path, &psi))
{
PWSTR szName;
if (S_OK == psi->GetDisplayName(SIGDN_NORMALDISPLAY, &szName))
{
DbgPrint("NORMALDISPLAY>%S\n", szName);
CoTaskMemFree(szName);
}
if (S_OK == psi->GetDisplayName(SIGDN_FILESYSPATH, &szName))
{
DbgPrint("FILESYSPATH>%S\n", szName);
CoTaskMemFree(szName);
}
psi->Release();
}
}
void tt()
{
if (0 <= CoInitialize(0))
{
tt(L"Recycle Bin");
tt(L"Startup");
CoUninitialize();
}
}
source to share