Peeking inside an IShellItem to see what it's made of


Windows XP introduced the IShell­Item interface which represents an item in the shell namespace. This encapsulates what traditionally is represented by a pair of things, the the IShell­Folder interface and an ITEMID_CHILD. The shell item lets you carry just one object around instead of two.

Another way of representing an item in the shell namespace is in the form of a single ITEMID_ABSOLUTE, and you can also create a shell item from that.

Creating a single unit of currency to represent a shell item tries to solve the problem of having to exchange money every time you cross a boundary. The IShell­Item also gives you some methods which simplifies various operations by wrapping low-level methods on IShell­Folder. For example, the IShell­Item::Bind­To­Handler method figures out the right way to get the item you ask for rather than making you puzzle out the arcane rules behind IShell­Folder::Bind­To­Object, IShell­Folder::Bind­To­Storage, IShell­Folder::Create­View­Object, IShell­Folder::Get­UI­Object­Of, and more.

But what if you need something that IShell­Item doesn't provide a convenience wrapper for? Then you need to peek inside.

If you want to peek inside and get the IShell­Folder and ITEMID_CHILD, you can use the IParent­And­Item interface, specifically, the IParent­And­Item::Get­Parent­And­Item method. Once nice thing about the IParent­And­Item::Get­Parent­And­Item method is that you can pass nullptr for the things you aren't interested in.

Alternatively, if you want to peek inside and get the ITEMIDLIST_ABSOLUTE, then you can use the IPersist­ID­List::Get­ID­List method to suck it out. We saw this a while back, but I'll repeat it here just so the information is all in one place.

If you are willing to abandon Windows XP support, you can use the SH­Get­ID­List­From­Object function which knows how to do this. (It tries other things, too.)

Okay, let's take things out for a spin. We'll get the normal display name for a shell item in four ways:

  • By asking the item directly.
  • By using the IShell­Folder::Get­Display­Name method.

  • By using the IPersist­ID­List::Get­ID­List method, and then the SH­Get­Name­From­ID­List function.

  • By using the SH­Get­ID­List­From­Object function, and then the SH­Get­Name­From­ID­List function.

If all goes well, we should get the same string printed each time.

Remember that Little Programs do little to no error checking.

#include <windows.h>
#include <shlobj.h>
#include <atlbase.h>
#include <atlalloc.h>
#include <stdio.h>     // horrors! mixing C and C++!

void PrintNameDirectlyFromItem(IShellItem* item)
{
    CComHeapPtr<wchar_t> name;
    item->GetDisplayName(SIGDN_NORMALDISPLAY, &name);
    _putws(name);
}

void PrintNameViaIShellFolder(IShellItem* item)
{
    CComPtr<IShellFolder> folder;
    CComHeapPtr<ITEMID_CHILD> child;
    CComQIPtr<IParentAndItem>(item)->GetParentAndItem(nullptr, &folder, &child);
    STRRET ret;
    folder->GetDisplayNameOf(child, SHGDN_NORMAL, &ret);
    CComHeapPtr<wchar_t> name;
    StrRetToStrW(&ret, child, &name);
    _putws(name);
}

void PrintNameViaAbsoluteIDList(IShellItem* item)
{
    CComHeapPtr<ITEMIDLIST_ABSOLUTE> absolute;
    CComQIPtr<IPersistIDList>(item)->GetIDList(&absolute);
    CComHeapPtr<wchar_t> name;
    SHGetNameFromIDList(absolute, SIGDN_NORMALDISPLAY, &name);
    _putws(name);
}

void PrintNameViaAbsoluteIDList2(IShellItem* item)
{
    CComHeapPtr<ITEMIDLIST_ABSOLUTE> absolute;
    SHGetIDListFromObject(item, &absolute);
    CComHeapPtr<wchar_t> name;
    SHGetNameFromIDList(absolute, SIGDN_NORMALDISPLAY, &name);
    _putws(name);
}

int main(int, char**)
{
    CCoInitialize init;

    CComPtr<IShellItem> item;
    SHGetKnownFolderItem(FOLDERID_Downloads, KF_FLAG_DEFAULT, nullptr, IID_PPV_ARGS(&item));

    PrintNameDirectlyFromItem(item);
    PrintNameViaIShellFolder(item);
    PrintNameViaAbsoluteIDList(item);
    PrintNameViaAbsoluteIDList2(item);

    return 0;
}

Bonus chatter: When you create a shell item, it takes the things you created it from, and it produces the other equivalent things on demand. For example, if you create a shell item from an absolute item ID list, and then you ask for the folder and child item ID, it will convert the absolute item ID list into a folder and child item ID list. (It also caches the result so that the next time you ask, it'll be able to answer the question more quickly.)

Comments (4)
  1. skSdnW says:

    Not sure if I fully agree with your description of Bind­To­Handler. It does take away some of the pain but it still requires you to pass in a BHID. If you need a IContextMenu you still need to know that BHID_SFUIObject is the correct BHID because it maps to IShellFolder::Get­UI­Object­Of...

  2. Yuri Khan says:

    Nitpicker’s corner: You forgot to roll your dice to determine today’s COM smart pointer library!

  3. Ivo says:

    How safe are IShellItems to use between threads? Let's say I enumerate a folder on a background thread, but use the items on the main UI thread. Is that allowed?

    I've always converted shell items to ITEMIDLIST_ABSOLUTE for passing between threads or for long-term storage in data structures. But your "Bonus chatter" remark makes me thing that's been unnecessary.

    1. IShellItems are COM objects and therefore follow standard COM rules, which includes "if you want to use the object in a different apartment, you must marshal it." ITEMIDLIST_ABSOLUTE is still useful if you would prefer to marshal things yourself, or to serialize them for later use.

Comments are closed.

Skip to main content