Obtaining the parsing name (and pidl) for a random shell object

The parsing name for a shell item is handy, because it lets you regenerate the item later. Actually, the pidl for the shell item is even better, because that is the official way of saving and restoring objects. It's the pidl that gets saved in a shortcut, and since shortcuts can be copied around from machine to machine, pidls must be transportable and forward compatible. (A shortcut file created on Windows XP needs to keep working on all future versions of Windows.)

Here's a handy little tool for grabbing the parsing name and pidl for a random shell object. Start with our scratch program, and add in the Simple­Drop­Target class, with the following tweaks:

 SimpleDropTarget() : m_cRef(1) { /* g_ppr->AddRef(); */ }
 ~SimpleDropTarget() { g_ppr->Release(); }

 // *** IDropTarget ***
 STDMETHODIMP DragEnter(IDataObject *pdto,
    DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect)
  *pdwEffect &= DROPEFFECT_LINK;
  return S_OK;

   POINTL ptl, DWORD *pdwEffect)
  *pdwEffect &= DROPEFFECT_LINK;
  return S_OK;

We are not a COM local server, so we won't worry about managing our process reference. And we will accept anything that has a pidl, so we say that we will accept objects via linking. (The original code accepted by copying, which would have made us reject non-copyable objects.)

Now we can hook these up to our scratch program.

OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
  g_hwndChild = CreateWindow(
      TEXT("edit"), nullptr, ES_MULTILINE |
      0, 0, 0,0, hwnd, (HMENU)1, g_hinst, 0);
  SimpleDropTarget *psdt = new(std::nothrow) SimpleDropTarget();
  if (psdt) {
    RegisterDragDrop(hwnd, psdt);
  return TRUE;

OnDestroy(HWND hwnd)

    // Change CoInitialize and CoUninitialize to Ole
    if (SUCCEEDED(OleInitialize(NULL))) {

Finally, we need to say what to do when the drop occurs.

void AppendText(LPCWSTR psz)
  SendMessageW(g_hwndChild, EM_REPLACESEL, 0, (LPARAM)psz);

void OpenFilesFromDataObject(IDataObject *pdto)
  CComPtr<IShellItemArray> spsia;
  if (SUCCEEDED(SHCreateShellItemArrayFromDataObject(
                                  pdto, IID_PPV_ARGS(&spsia)))) {
    CComPtr<IEnumShellItems> spenum;
    if (spenum) {
      for (CComPtr<IShellItem> spsi;
           spenum->Next(1, &spsi, nullptr) == S_OK;
           spsi.Release()) {
        CComHeapPtr<wchar_t> spszName;
        if (SUCCEEDED(spsi->GetDisplayName(
                     SIGDN_DESKTOPABSOLUTEPARSING, &spszName))) {
        CComHeapPtr<ITEMIDLIST_ABSOLUTE> spidl;
        if (SUCCEEDED(CComQIPtr<IPersistIDList>(spsi)->
                                            GetIDList(&spidl))) {
          UINT cb = ILGetSize(spidl);
          BYTE *pb = reinterpret_cast<BYTE *>
          for (UINT i = 0; i < cb; i++) {
            WCHAR szHex[4];
            StringCchPrintf(szHex, ARRAYSIZE(szHex),
                            L"%02X ", pb[i]);

When the drop occurs, we convert the data object into a shell item array, enumerate the items, and print the parsing name for the item as well as a hex dump of the pidl associated with the item.

I guess we need some header files.

#include <shlobj.h>
#include <strsafe.h>
#include <atlbase.h>
#include <atlalloc.h>

Run this program and drop the Recycle Bin onto it, say.

14 00 1F 78 40 F0 5F 64 81 50 1B 10 9F 08 00 AA 00 2F 95 4E 00 00 

This tells you two things. First, that if you want to generate the Recycle Bin from a parsing name, you can use that string that starts with two colons.

var shell = new ActiveXObject("Shell.Application");
var recycleBin = shell.Namespace(
var items = recycleBin.Items();
for (var i = 0; i < items.Count; i++) {

Of course, there is a predefined enumeration for the Recycle Bin, so this was a bit of a waste. You could've just written

var recycleBin = shell.Namespace(10);

But this technique generalizes to other locations in the shell namespace that do not have a special shorthand value.

The second thing the program tells you is that if you want to generate the Recycle Bin from a pidl, you can just use that chunk of bytes. Okay, that's not quite so interesting from a scripting point of view, but if you're manipulating pidls, this can be quite handy.

We'll use this program a little bit in a few weeks, but at this point, it's just a "Little Program" for today.

Comments (5)
  1. skSdnW says:

    Parsing names don't always round trip back to a pidl. The control panel and 3rd party stuff can be problematic.

  2. John Doe says:

    In the for statement, is spsi.Release() really necessary?

    I wonder, do you use an IDE when building these examples? If not, do you know all of this from the top of your head, or are you retrieving past source code snippets?

    I mean, I've done code similar to this (in terms of ATL templates, smart pointers and Win32) with a plain text editor (don't ask…), but it took quite a few round trips between the editor and the documentation.

    [Remove the spsi.Release() and run the code and see what happens. (A debug build will identify the problem more quickly.) -Raymond]

  3. John Doe says:

    @Raymond, you're entirely right. It would leak memory. A debug build would also complain about the pointer not being NULL right on the second iteration's test, the purpose indeed seems to be to make the developer stumble on such cases.

    For the matter of the mental exercise, I didn't compile it, but I peeked the implementation of CComPtr and CComPtrBase.

    Somehow, I was hoping C++ would do the RAII dispose pattern when resetting the pointer, but that's not the case. This is what so much time away from C++ does, especially coming from more dynamic environments. In some languages, you can setup e.g. a setter that releases the previous pointer if not NULL before setting it to the new value, together with syntax/macros that allocate temporary pointers to "foreign calls", so you never change the field directly without notification.

  4. Joshua says:

    Microsoft's C++ frameworks predate modern C++ thought and so don't understand RAII. Can't really blame them, but the stuff looks old and mis-designed now.

  5. John Doe says:

    @Joshua, having & and * operators that return the address of the inner pointer (with an assertion) is relatively modern C++. What C++ doesn't do for you, even a more modern one, is a code transformation around the & operator that:

    – creates a temporary variable of T*

    – uses the smart pointer's pointer getter to initialize the temp

    – invokes the function with the temp

    – uses the smart pointer's pointer setter with the value of temp

    And it shouldn't, not now that we rely on how the & and * operators work. But one may do this with further RAII (there goes the concise for loop) or with a template.

    To correct myself, the order of execution in the setter must be to first AddRef the new value if not NULL and then Release the old value if not NULL: blogs.msdn.com/…/108395.aspx

    At first thought, it just sounds so "obvious" we should Release the old value and AddRef the new one, but it only becomes clear there is an order it must be done with further insight, when you realize the new value can be an interface pointer sharing a refcount (e.g. same underlying object) with the old value.

Comments are closed.

Skip to main content