Creating a simple pidl: For the times you care enough to send the very fake

Raymond Chen

I’ll assume that we all know what pidls are and how the shell namespace uses them. That’s the prerequisite for today.

A simple pidl is an item ID list that refers to a file or directory that may not actually exist. It’s a way of playing “what if”: “If there were a file or directory at this location, here is what I would have created to represent it.” For the times you care enough to send the very fake.

We’ve seen these things in action with the SHGFI_USE­FILE­ATTRIBUTES flag, which tells the SH­Get­File­Info function, “Pretend that the file/directory exists with the attributes I specified, and tell me what the icon would be, were that item to actually exist.”

Internally, the SH­Get­File­Info function creates one of these “simple pidls”, and then asks the simple pidl for its icon.

Note that a simple pidl is really a special case of a pidl created from a WIN32_FIND_DATA. When you parse a display name with a custom bind context, and the bind context has a STR_FILE_SYS_FIND_DATA bind context object, then that object is used to control the information placed into the pidl instead of getting the information from the file system.

Here’s a program that creates a simple pidl and then does a few simple things with it. (Note that the Parsing with Parameters sample covers this topic too, so if you don’t like the way I did it, you can look to see how somebody else did it.)

#define STRICT_TYPED_ITEMIDS
#include <new>
#include <windows.h>
#include <ole2.h>
#include <oleauto.h>
#include <shlobj.h>
#include <propkey.h>
#include <atlbase.h>
#include <atlalloc.h>

class CFileSysBindData : public IFileSystemBindData { public: static HRESULT CreateInstance( _In_ const WIN32_FIND_DATAW *pfd, _In_ REFIID riid, _Outptr_ void **ppv);

// *** IUnknown *** IFACEMETHODIMP QueryInterface( _In_ REFIID riid, _Outptr_ void **ppv) { *ppv = nullptr; HRESULT hr = E_NOINTERFACE; if (riid == IID_IUnknown || riid == IID_IFileSystemBindData) { *ppv = static_cast<IFileSystemBindData *>(this); AddRef(); hr = S_OK; } return hr; }

IFACEMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&m_cRef); }

IFACEMETHODIMP_(ULONG) Release() { LONG cRef = InterlockedDecrement(&m_cRef); if (cRef == 0) delete this; return cRef; }

// *** IFileSystemBindData *** IFACEMETHODIMP SetFindData(_In_ const WIN32_FIND_DATAW *pfd) { m_fd = *pfd; return S_OK; }

IFACEMETHODIMP GetFindData(_Out_ WIN32_FIND_DATAW *pfd) { *pfd = m_fd; return S_OK; }

private: CFileSysBindData(_In_ const WIN32_FIND_DATAW *pfd) : m_cRef(1) { m_fd = *pfd; } private: LONG m_cRef; WIN32_FIND_DATAW m_fd; };

HRESULT CFileSysBindData::CreateInstance( _In_ const WIN32_FIND_DATAW *pfd, _In_ REFIID riid, _Outptr_ void **ppv) { *ppv = nullptr; CComPtr<IFileSystemBindData> spfsbd; HRESULT hr = E_OUTOFMEMORY; spfsbd.Attach(new (std::nothrow) CFileSysBindData(pfd)); if (spfsbd) { hr = spfsbd->QueryInterface(riid, ppv); } return hr; }

The CFile­Sys­Bind­Data object is extraordinarily boring. It simply implements IFile­System­Bind­Data, which is a simple interface that just babysits a WIN32_FIND_DATA structure. (There is also a IFile­System­Bind­Data2 interface which babysits a little more information, but for the purpose of this program, we’re interested only in the WIN32_FIND_DATA.)

HRESULT CreateBindCtxWithOpts(
 _In_ BIND_OPTS *pbo, _Outptr_ IBindCtx **ppbc)
{
 CComPtr<IBindCtx> spbc;
 HRESULT hr = CreateBindCtx(0, &spbc);
 if (SUCCEEDED(hr)) {
  hr = spbc->SetBindOptions(pbo);
 }
 *ppbc = SUCCEEDED(hr) ? spbc.Detach() : nullptr;
 return hr;
}

A bind context is basically a string-indexed associative array of COM objects. There is also a BIND_OPTS (or BIND_OPTS2) structure in there, but the things most people care about are the object parameters. They provide an extensible method of passing arbitrary parameters to a function. (Think of it as the COM version of the JavaScript convention of jamming random junk into an Options parameter.) You start with a IBind­Ctx parameter, and any time you need to add a new flag or parameter, you just stuff it into the IBind­Ctx. If you just want to add a new boolean flag, you can even ignore the contents of the object parameter and merely base your behavior on whether the parameter exists at all.

HRESULT AddFileSysBindCtx(
 _In_ IBindCtx *pbc, _In_ const WIN32_FIND_DATAW *pfd)
{
 CComPtr<IFileSystemBindData> spfsbc;
 HRESULT hr = CFileSysBindData::CreateInstance(
  pfd, IID_PPV_ARGS(&spfsbc));
 if (SUCCEEDED(hr)) {
  hr = pbc->RegisterObjectParam(STR_FILE_SYS_BIND_DATA,
                                spfsbc);
 }
 return hr;
}

To add a file system bind parameter, you just create an object which implements IFile­System­Bind­Data and register it with the bind context with the string STR_FILE_SYS_FIND_DATA.

HRESULT CreateFileSysBindCtx(
 _In_ const WIN32_FIND_DATAW *pfd, _Outptr_ IBindCtx **ppbc)
{
 CComPtr<IBindCtx> spbc;
 BIND_OPTS bo = { sizeof(bo), 0, STGM_CREATE, 0 };
 HRESULT hr = CreateBindCtxWithOpts(&bo, &spbc);
 if (SUCCEEDED(hr)) {
  hr = AddFileSysBindCtx(spbc, pfd);
 }
 *ppbc = SUCCEEDED(hr) ? spbc.Detach() : nullptr;
 return hr;
}

The Create­File­Sys­Bind­Ctx function simply combines the two steps of creating a bind context and then adding a file system bind parameter to it. In casual conversation, a bind context is often named after the parameter inside it. In this case, we have a bind context with a file system bind parameter, so we call it a “file system bind context”.

HRESULT CreateSimplePidl(
 _In_ const WIN32_FIND_DATAW *pfd,
 _In_ PCWSTR pszPath, _Outptr_ PIDLIST_ABSOLUTE *ppidl)
{
 *ppidl = nullptr;
 CComPtr<IBindCtx> spbc;
 HRESULT hr = CreateFileSysBindCtx(pfd, &spbc);
 if (SUCCEEDED(hr)) {
  hr = SHParseDisplayName(pszPath, spbc, ppidl, 0, nullptr);
 }
 return hr;
}

This is where everything comes together. To create a simple pidl, we take the WIN32_FIND_DATAW containing the metadata we want to use, put it inside a file system bind context, then use that bind context to parse the file name. The presence of a file system bind context tells the parser, “Trust me on this, just go with what’s in the bind context.” It suppresses all disk access, and the final pidl will describe an item that exactly matches the metadata you provided, whether that accurately reflects reality or not. (You can also pass the bind context to SHCreate­Item­From­Parsing­Name if you prefer to get an IShell­Item.)

Okay, let’s take this out for a spin.

void DoStuffWith(_In_ PCIDLIST_ABSOLUTE pidl)
{
 // Print the file name
 wchar_t szBuf[MAX_PATH];
 if (SHGetPathFromIDListW(pidl, szBuf)) {
  wprintf(L”Path is \”%ls\”\n”, szBuf);
 }

// Print the file size CComPtr<IShellFolder2> spsf; PCUITEMID_CHILD pidlChild; if (SUCCEEDED(SHBindToParent(pidl, IID_PPV_ARGS(&spsf), &pidlChild))) { CComVariant vt; if (SUCCEEDED(spsf->GetDetailsEx(pidlChild, &PKEY_Size, &vt))) { if (SUCCEEDED(vt.ChangeType(VT_UI8))) { wprintf(L”Size is %I64u\n”, vt.ullVal); } } } }

int __cdecl wmain(int argc, PWSTR argv[]) { CCoInitialize init; if (SUCCEEDED(init)) { WIN32_FIND_DATAW fd = {}; fd.dwFileAttributes = FILE_ATTRIBUTE_NORMAL; fd.nFileSizeLow = 42; CComHeapPtr<ITEMIDLIST_ABSOLUTE> spidlSimple; if (SUCCEEDED(CreateSimplePidl(&fd, L”Q:\\Whatever.txt”, &spidlSimple))) { DoStuffWith(spidlSimple); } } return 0; }

Our test program asks for a simple pidl to Q:\Whatever.txt, and then prints information from it. Observe that the creation of the simple pidl succeeds even though you probably don’t have a Q: drive, and even if you did, the code never tried to access it. And when we ask the pidl, “Hey, what’s the file size?” it retrieves the fake value 42 we passed in the WIN32_FIND_DATAW structure.

Sure, that was kind of artificial, but so-called simple pidls are handy if you want to talk about an object on slow media (such as a network share) without actually accessing the target device.

Exercise: What changes are necessary in order to create a simple pidl that refers to a file with illegal characters in its name? Hint: STR_NO_VALIDATE_FILENAME_CHARS.

0 comments

Discussion is closed.

Feedback usabilla icon