Undocumented
Windows ® 95
Shell Item Identifiers
I tem identifiers and item identifier lists are what the shell
uses to identify items in the its namespace. An item
xxxxxxxxxxxxxxx
identifier is much like a filename, and is unique to its parent
folder. An item identifier list (IDL) is like a full pathname - it
traces the path from the desktop to the item.
      The only thing that is documented about an item identi-
fier is the size field in the first two bytes. The format of the
rest of the structure is dependent on the parent folder. An
IDL (defined by the ITEMIDLIST structure) is really just a
bunch of item identifiers joined together and terminated
with a 16-bit zero. Most shell functions require or return a
pointer to an IDL (a PIDL).
      All of this is explained in more detail in the API help and
is basically all you need to know in order to manipulate
IDLs. There is no real need for most of the undocumented
functions described in this section. However, working with
variable length, byte-aligned structures can be rather
messy, so it's convenient to have the shell do most of the
work for you.
Memory Allocation
The first trick in dealing with IDLs is that the memory al-
most always has to be allocated and released using the
shell's memory allocator (an IMalloc interface retrieved
using the SHGetMalloc function). When exchanging PIDLs,
it is often the responsibility of the application to free a PIDL
created by the shell and vice versa. It is therefore important
that you're both using the same memory allocator.
      Fortunately there are two simple functions, SHAlloc
and SHFree, which can allocate and release memory using
the shell allocator, without the effort of obtaining an IMalloc
interface. Like all the
functions in this sec-
tion, they're exported
from SHELL32.DLL and
they're exported by or-
dinal (ordinals 196 and
LPVOID WINAPI SHAlloc(ULONG cb);
void WINAPI SHFree(LPVOID pv);
void WINAPI ILFree(LPCITEMIDLIST pidl);
Figure 1  Memory Related Functions
195 respectively). The functions declarations are shown in
Figure 1.
      When freeing a PIDL, though, it is probably preferable
to use the ILFree function (ordinal 155), even if it's not
much more than a wrapper around SHFree. It does also
make sure the PIDL isn't NULL before calling SHFree, but I
would expect the IMalloc implementation to take care of
that anyway. The declaration for ILFree can also be seen
in Figure 1.
From Paths to PIDLs
Typically PIDLs are returned to you by certain functions and
passed around to other functions, without you ever really
knowing anything about the internal structure of the IDL and
without you ever needing to create one. Obviously if you're
implementing a namespace extension, you'll need to create
item identifiers for the objects in your own virtual folders,
but the format of those item identifiers is specific to your
folder and you can do whatever you want with them. 
      But what happens if you need to create a PIDL for
somebody else's namespace, like a path in the file system?
The 'documented'
method would be
to get an IShell-
Folder interface
for the relevant
folder and then
call Parse-
DisplayName for
the name you
need to convert
into a PIDL. For a
filename, you
would typically
HRESULT WINAPI SHILCreateFromPath(
  LPCSTR lpszPath, 
  LPITEMIDLIST *ppidl, 
  ULONG *pdwAttributes);

LPITEMIDLIST WINAPI ILCreateFromPath(
  LPCSTR lpszPath);

LPITEMIDLIST WINAPI SHSimpleIDListFromPath(
  LPCSTR lpszPath);
Figure 2  Creation Functions
use the desktop IShellFolder (SHGetDesktopFolder).
      However, if you're feeling lazy, you might find it easier
to make use of one of the three functions in Figure 2.
SHILCreateFromPath is basically just a wrapper around
the desktop folder's ParseDisplayName, and ILCreate-
FromPath is just a simplified wrapper around SHILCreate-
FromPath. SHSimpleIDListFromPath, however, im-
plements the whole process itself. The ordinal values are
28, 157 and 162 respectively.
      I'm not entirely sure what the difference is between the
two methods, but I suspect that SHSimpleIDListFromPath
is slightly faster but doesn't cache all the details that would
normally be stored in an item identifier. For example, if you
pass a PIDL created by SHSimpleIDListFromPath to the
SHBrowseForFolder function, you will notice that the dis-
play name and icon aren't always correct. I could be totally
wrong about the cause though.
Parsing a PIDL
If, for some reason, you need to iterate through each item
identifier in an IDL, you will probably find ILGetNext very
useful. When given a PIDL (or a pointer to any item in the
IDL for that matter), the function will return a pointer to the
next item identifier in the IDL. If the PIDL is NULL or is
already pointing to the last item in the IDL, the function will
return NULL. For the specific case of finding the last item in
the IDL, you can just call ILFindLastID.
      An even more specific form of search is the ILFind-
Child function. Given a parent PIDL and a child PIDL, it will
return a pointer to the unique
portion of the child. For
example, if you passed it PIDLs
for 'C:\DIR' as the parent and
'C:\DIR\FILE.TXT' as the child, it
would return a pointer to the
portion of the child PIDL that
represents 'FILE.TXT'. If the
given child is not really a child
of the parent, the function will
return NULL.
      The ordinals for these three
LPITEMIDLIST WINAPI ILGetNext(
  LPCITEMIDLIST pidl);

LPITEMIDLIST WINAPI ILFindLastID(
  LPCITEMIDLIST pidl);

LPITEMIDLIST WINAPI ILFindChild(
  LPCITEMIDLIST pidlParent, 
  LPCITEMIDLIST pidlChild);
Figure 3  Parsing Functions
functions are 153, 16 and 24 respectively. The function
declarations can be seen in Figure 3.
Copying and Combining
Something that is even more useful when dealing with
PIDLs, is the ability to make a copy of a PIDL passed to you
by the shell. When passed an existing PIDL, the ILClone
function will return a new identical copy of that PIDL. The
ILCloneFirst function, on the other hand, will return a new
PIDL containing only the first item identifier from the source
PIDL. If you need a copy of the last item identifier, you
could use a combination of ILFindLastID and ILCloneFirst.
For other portions of the IDL, you would have to use ILGet-
Next and ILCloneFirst.
      If you want to combine two PIDLs together, you would
use the ILCombine function. Given two PIDLs, it will create
a new PIDL containing the two
source IDLs joined together
consecutively. If you want to
combine a single item identifier
with a PIDL, you would use the
ILAppendID function. It can
be used to append an ITEMID
to either the beginning or the
end of an existing IDL.
However, unlike ILCombine,
the orginal PIDL is destroyed by
this operation. The ILAppend-
ID function can also be used to
create a PIDL from a item iden-
tifier alone, by passing a NULL
for the PIDL.
LPITEMIDLIST WINAPI ILClone(
  LPCITEMIDLIST pidl);

LPITEMIDLIST WINAPI ILCloneFirst(
  LPCITEMIDLIST pidl);

LPITEMIDLIST WINAPI ILCombine(
  LPCITEMIDLIST pidl1, 
  LPCITEMIDLIST pidl2);

LPITEMIDLIST WINAPI ILAppendID(
  LPITEMIDLIST pidl, 
  LPCSHITEMID lpItemID, 
  BOOL bAddToEnd);
Figure 4  Functions for Copying and Combining
      The ordinals for these four functions are 18, 19, 25 and
154 respectively. The function declarations can be seen in
Figure 4.
Death and Destruction
If you need to delete an entire PIDL, you would obviously
just use the ILFree funcion. If you need to remove the last
item identifier from the end of an IDL
you would use the ILRemoveLastID
function (see Figure 5). The return va-
lue is TRUE if the operation was suc-
cessful. Note, however, that it doesn't
BOOL WINAPI ILRemoveLastID(
  LPITEMIDLIST pidl);
Figure 5  ILRemoveLastID
actually free any memory - it just resets the end of list
marker. The ordinal value is 17. 
      Unfortunately that is the only delete function that
exists. If you want to remove an item identifier from the
beginning of an IDL, the best you can do is use a combina-
tion of ILGetNext and ILClone and then delete the original
PIDL with ILFree. Other delete operations would probably
be even more complicated, but I'm assuming they aren't all
that common.
Equality and Parenthood
If you need to determine if two PIDLs are equal, you would
use the ILIsEqual function. If you need to determine if a
particular PIDL is a child of another PIDL, you would call the
ILIsParent function passing it the suspected parent and
child. If you require that the child be
an immediate descendent (i.e. it is a
member of the parent folder, not a
member of a subfolder), then you
would set the last parameter of the
function to TRUE. See Figure 6 for the
function declarations. 
      Note that the equality of two IDLs
is not determined with a binary com-
parison. Both of the above functions
use the desktop folder's CompareIDs
BOOL WINAPI ILIsEqual(
  LPCITEMIDLIST pidl1, 
  LPCITEMIDLIST pidl2);

BOOL WINAPI ILIsParent(
  LPCITEMIDLIST pidlParent, 
  LPCITEMIDLIST pidlChild, 
  BOOL bImmediate);
Figure 6  Testing Functions
function to perform equality tests. The ILIsParent function
obviously only tests whether the 'base' PIDL of the child is
equal to the parent PIDL
      The ordinals for these functions are 21 and 23 respec-
tively.
Saving and Loading
HRESULT WINAPI ILSaveToStream(
  LPSTREAM pstrm, 
  LPCITEMIDLIST pidl);

HRESULT WINAPI ILLoadFromStream(
  LPSTREAM pstrm, 
  LPITEMIDLIST *ppidl);
Figure 7  Streaming Functions
I can't honestly think of any rea-
son for using the next two
functions, but if you ever need to
save or load a PIDL using
streams you would use the
ILSaveToStream and ILLoad-
FromStream functions (see
Figure 7). The ordinal values are
27 and 26 respectively.
      Note that when calling ILLoadFromStream, the PIDL
pointed to by the ppidl parameter must either be NULL or
must be a valid PIDL. The function will always attempt to
free the PIDL before overwriting it, which will obviously
have disastrous consequences if it is just a random uninitia-
lized value. 
Global Memory Allocation
As I mentioned earlier, the memory for an IDL should al-
most always be allocated using the shell's memory alloca-
tor. However, there are two functions that use a different
method of allocating and freeing memory, ILGlobalClone
and ILGlobalFree (ordinals 20 and 156). The function dec-
larations can be seen in Figure 8.
      On Windows NT, these global functions just use the
default process heap (as returned by GetProcessHeap).
This led me to believe that heap allocations were in some
way more efficient than the
shell allocator, and the global
functions were only used in-
ternally by the shell for rea-
sons of efficiency.
      However, on Windows
95, most of the internal struc-
tures in the shell need to be
LPITEMIDLIST WINAPI ILGlobalClone(
  LPCITEMIDLIST pidl)

void WINAPI ILGlobalFree(
  LPCITEMIDLIST pidl);
Figure 8  Global Memory Functions
shared between all instances of the DLL. In the case of
PIDLs, the memory used when allocating them obviously
has to be shareable as well. ILGlobalClone solves this
problem by using an undocumented shared heap for the
allocations, conveniently making the pointers accessible
from anywhere.
The Rest
If you need to determine the size of an IDL, you would use
the ILGetSize function. If you need to determine the display
name of a PIDL you could either use
the ILGetDisplayName function, or
the documented SHGetPathFrom-
IDList. ILGetDisplayName basically
just calls the desktop shell folder's
GetDisplayNameOf function with
the flag SHGDN_FORPARSING. From
what I can make out, that eventually
leads to SHGetPathFromIDList
UINT WINAPI ILGetSize(
  LPCITEMIDLIST pidl);

BOOL WINAPI ILGetDisplayName(
  LPCITEMIDLIST pidl, 
  LPSTR lpszName);
Figure 9  ILGetSize and ILGetDisplayName
being called anyway, so there doesn't really seem to be any
need to use the undocumented function. 
      The ordinal values for ILGetSize and ILGetDisplay-
Name are 152 and 15 respectively. See Figure 9 for the
function declarations.
Windows NT and Unicode Strings
One last thing I should mention. Undocumented functions
on Windows NT almost always expect string parameters to
be in Unicode. So anywhere you see an LPSTR or an
LPCSTR type, it should actually be LPWSTR or LPCWSTR
when running on Windows NT.
BACK TO HOME
Copyright © 1998-1999 James Holderness. All Rights Reserved
Page last modified: August 9th, 1998