Home Home Archives About red box
Username:
Password:
 
rss

Filed Under Delphi
digg Window's Shell Namespace
visits: 741 | score: 0 
posted by sysrpl on Friday May 5, 2006 4:48 AM

ne of the primary uses of computers has always been to store and retrieve information. Through the years, the amount of information handled by personal computers has grown at an amazing rate. New kinds of information, and a demand for faster larger storage has impacted they way we handle and view data. This article discusses how Microsoft has choosen to organize that information on the desktop for the last 10 years.

The Windows shell, better known as Windows Explorer, uses a single hierarchy to contain everything connected to, and stored on your computer. This hierarchy is known as the shell's namespace. In our example application, we will browse the shell's namespace hierarchy. The example demonstrated how every item inside the shell namespace is uniquely identified by an item identifier list.

Item lists play a pivotal role when working with the shell namespace. We're going to explain how and why you would want to use them. We'll show you exactly what item lists are, how to use them in conjunction with shell folders, and demonstrate shell namespace navigation. Finally, we'll show you how to access user interface elements with item lists and provide additional examples of their uses.

Item lists compared to system paths

File system paths are still an important part of your computer. They are powerful, relatively easy to work with, and simple to understand. If you are intent on working with only files or folders, then file paths would be the preferred way to identify the contents of your disk drive.

Although file system paths can be useful for identifying objects located in the file system part of the namespace, they cannot, however, be used to reference virtual shell objects. A virtual shell object is an item in the namespace that does not has a direct representation in the file system. Examples of virtual shell objects are My Computer, the Control Panel, and the Recycle Bin. Currently, more powerful item identifier lists are the preferred means of identification for namespace objects. When using item identifier lists, you computer's contents can be thought of as a collection of objects rather that a set of files and folders.

Manipulating item lists

Before we can access objects in the shell's namespace, we must have some way of addressing them. Internally, item identifier lists are what the shell uses to identify objects its namespace.

Let's examine the structure of an item list.

An item identifier list is comprised of two or more item identifiers. The only thing documented about an item identifier is it's first two bytes which contain a size field. This size field tells us the distance to the beginning of the next structure in an item list. The details of the information between the size fields should be treated as opaque data. Figure two below shows the internal structure of an item list. The list is a set item identifiers joined together and terminated by a zero filled word. The data contained in the "My Computer" section is binary information meaningful only to the shell.

When working with the namespace, most of Microsoft's shell functions will require you to identify objects through a pointer to an item identifier list. This translates into the inevitable need to manipulate item lists. Since manipulating variable length byte aligned binary data structures can become rather messy, it's convenient to have a set of functions and procedures to handle this work for you. It's good that Microsoft's uses an internal set of functions to handle this problem, but unfortunately for us developers these functions are completely undocumented. In the table below, I have gathered a list of some of Microsoft's undocumented item list routines. These routines are exported by the Shell32.dll library, and can be imported only by ordinal value.

Function Category Ordinal Value
ILAppendID copying 154
ILClone copying 18
ILCloneFirst copying 19
ILCombine copying 25
ILCreateFromPath construction 153
ILFindChild parsing 157
ILFindLastID parsing 24
ILFree destruction 155
ILGetNext parsing 16
ILGetSize parsing 152
ILIsEqual comparison 21
ILIsParent comparison 23
ILRemoveLastID destruction 17

At the end of this article is a package that contains the full declaration of these functions. Be aware that when working with these undocumented functions, there is no guarantee that they will always work or be available. If this is a concern for you, then you will be delighted to know that I have translated all of the above functions into native Object Pascal equivalents.

Before we discuss the details of the functions listed above, you should know that there are essentially two types of item lists. Pictured in figure two is a schematic of what is called an absolute item list. An absolute item list can be compare to a fully qualified system path. Inside an absolute item list is everything the shell needs to identify an object in the namespace. Below in figure three is a representation of what is known as a relative item list. Both an absolute and relative item list might refer to the same object, but the relative list only has meaning in the context of its parent container. Again, it may be helpful to think of relative item lists as a relative system path such as "Bin\Delphi32.exe" compared to the absolute path "C:\Program Files\Delphi5\Bin\Delphi32.exe".

The functions in the table above has been grouped into a set of logical categories below I explain the usage of these function. Unless otherwise noted, these routines can be used in conjunction with both absolute and relative lists.

Construction

There are many ways to get an item list, but none as simple as the one below.

function ILCreateFromPath(lpszPath: PChar): PItemIDList;

The ILCreateFromPath function creates an absolute item list given a valid pointer to a character path string. This function is useful if you wish to easily convert a file system path to an item list.

Important: You are completely responsible for the memory occupied by an item identifier list. This applies to item lists you don't create explicitly. Item lists are allocated from a special object called the shell task allocator. Before you discard any item list, you should always free it using either the shell task allocator or the ILFree procedure discussed later. There are no exceptions to this rule

Destruction

The proper disposal of item lists is important if you plan to create robust applications.

procedure ILFree(pidl: PItemIDList);

The ILFree procedure releases an item list using the shell task allocator if the item list points a value other than nil. Every item list you touch should eventually make its way to this procedure.

function ILRemoveLastID(pidl: PItemIDList): Boolean;

The ILRemoveLastID function alters of contents of an item list, removing the last item. The return value is true if an item is removed from the list. This function does not actually free memory, but resets the end item list marker.

Comparison

When working with item lists, it is sometimes useful to test them against each other to find a relationship.

function ILIsEqual(pidl1, pidl2: PItemIDList): Boolean;

The ILIsEqual function tests whether two item lists reference the same namespace object.

function ILIsParent(pidlParent, pidlChild: PItemIDList; Immediate: Boolean): Boolean;

The ILIsParent function tests to determine if a child item list is contained within a parent list. The child list should be relative. If the Immediate parameter is true, then the function will only return a true value if the child list has a depth of one from the parent.

function ILIsRoot(pidl: PItemIDList): Boolean;

The ILIsRoot function is one of two functions not listed in undocumented function index. This function tests if an item list references the shell root.

Copying

These copying functions all create new item lists. You are responsible for freeing the memory returned from these functions.

function ILAppendID(pidl: PItemIDList; const ItemID: TSHItemID; AddToEnd: Boolean): PItemIDList;

The ILAppendID function adds an item to an item list. Unlike the other copying functions, this function will free the item list passed to it and return a new item list. If the AddToEnd parameter is true, the item will be appended to the end of the list, otherwise the item will place in the front.

function ILClone(pidl: PItemIDList): PItemIDList;

The ILClone is a useful function that will return duplicate of an item list.

function ILCloneFirst(pidl: PItemIDList): PItemIDList;

The ILCloneFirst function returns a clone of the first item in an item list.

function ILCombine(pidl1: PItemIDList; pidl2: PItemIDList): PItemIDList;

The ILCombine function concatenates two item lists.

Parsing

The parsing functions below do not create new item lists. Instead, they return a reference to list segments contained within their item list parameters.

function ILFindChild(pidlParent, pidlChild: PItemIDList): PItemIDList;

The ILFindChild function operates much like the ILIsParent function, except that the return value is the portion of a parent list that contains the child. If child is not contained within the parent, then return value is nil.

function ILFindLastID(pidl: PItemIDList): PItemIDList;

The ILFindLastID returns the last item of an item list.

function ILGetCount(pidl: PItemIDList): Integer;

The ILGetCount function is the other function not listed in undocumented function index. This function returns the number of items contained a list.

function ILGetNext(pidl: PItemIDList): PItemIDList;

The ILCombine function returns the next item in the a list. If there is no next item in the list, the return value is nil.

function ILGetSize(pidl: PItemIDList): Integer;

The ILCombine function returns the total number of bytes, less the two byte null terminator, occupied by an item list.

After having read the section above, you should have noticed that item lists are almost exclusively dynamically created and referenced through pointers.

Working with the shell namespace

Now that we have a complete understanding of item identifier lists, its time to turn our attention to working with the shell namespace. As has been stated previously, the shell namespace is a collection of shell objects arranged in a hierarchy. This hierarchy is rooted to a single shell object called the desktop.

The desktop serves as a starting point to navigate the shell namespace. From the desktop, it is possible to enumerate all the items in the shell. To begin the navigation we must first get a reference to the desktop. The can be accomplished using the following code:

var
  Desktop: IShellFolder;

begin
  OleCheck(SHGetDesktopFolder(Desktop));
  ...

From this code fragment you might be able to gather that the desktop is referenced through a COM interface. Specifically, the interface we use to work with the desktop, and with most every shell container, is the IShellFolder interface. I won't go into all the details of the IShellFolder interface in this article, as it is quite well documented in Microsoft's Platform SDK, but to briefly summarize, the IShellFolder interface allows us to enumerate the contents of the shell, set and retrieve the names of shell objects, query their attributes, and interact with user interface elements.

Here are a few examples of how to use the IShellFolder interface:

type
  TItemListArray = array of PItemIDList;
 
...
 
function GetShellItems(Folder: IShellFolder): TItemListArray;
const

  SHCONTF_ALL = SHCONTF_FOLDERS or SHCONTF_NONFOLDERS or SHCONTF_INCLUDEHIDDEN;
var
  EnumList: IEnumIDList;
  NewItem: PItemIDList;
  Dummy: Cardinal;
  I: Integer;
begin
  Result := nil;
  I := 0;
  if Folder.EnumObjects(0, SHCONTF_ALL, EnumList) = S_OK then
    while EnumList.Next(1, NewItem, Dummy) = S_OK do

    begin
      Inc(I);
      SetLength(Result, I);
      Result[I - 1] := NewItem;
    end;
end;

The GetShellFolders function above returns an array of item lists relative to a parent folder. The EnumObjects method retrieves an item list enumerator interface. With this function, you are responsible for eventually freeing all the items contained in the result.

function GetShellObjectName(Folder: IShellFolder; ItemList: PItemIDList): string;

var
  StrRet: TStrRet;
begin
  Folder.GetDisplayNameOf(ItemList, SHGDN_INFOLDER, StrRet);
  case StrRet.uType of
    STRRET_WSTR:
      begin

        Result := WideCharToString(StrRet.pOleStr);
        CoTaskMemFree(StrRet.pOleStr);
      end;
    STRRET_OFFSET: Result := PChar(Cardinal(ItemList) + StrRet.uOffset);
    STRRET_CSTR: Result := StrRet.cStr;
  end;
end;

The GetShellObjectName function returns a string representation of an item list relative to a parent folder.

Putting together the information we have so far, we can write a procedure that outputs the hierarchy of the shell namespace to any given number of levels.

procedure EnumShellNamespace(Strings: TStrings; Depth: Integer;
  Folder: IShellFolder = nil);
 
  procedure AddObjectName(Folder: IShellFolder; ItemList: PItemIDList;
    Level: Integer);
  var

    S: string;
  begin
    SetLength(S, Level * 2);
    FillChar(PChar(S)^, Length(S), ' ');
    Strings.Add(S + GetShellObjectName(Folder, ItemList));
  end;
 
  procedure EnumItems(Folder: IShellFolder; Level: Integer);
  var
    Items: TItemListArray;
    ItemList: PItemIDList;
    Flags: Cardinal;
    SubFolder: IShellFolder;
    I: Integer;
  begin

    Inc(Level);
    Items := GetShellItems(Folder);
    try
      for I := 0 to Length(Items) - 1 do
      begin
        ItemList := Items[I];
        AddObjectName(Folder, ItemList, Level);
        if Level < Depth then
        begin

          Flags := SFGAO_HASSUBFOLDER;
          OleCheck(Folder.GetAttributesOf(1, ItemList, Flags));
          if Flags and SFGAO_HASSUBFOLDER = SFGAO_HASSUBFOLDER then
          begin
            OleCheck(Folder.BindToObject(ItemList, nil, IID_IShellFolder,
              SubFolder));
            EnumItems(SubFolder, Level);
          end;
        end;
      end;
    finally
      for I := 0 to Length(Items) - 1 do

        ILFree(Items[I]);
    end;
  end;
 
begin
  Strings.BeginUpdate;
  try
    Strings.Clear;
    if Folder = nil then

    begin
      OleCheck(SHGetDesktopFolder(Folder));
      AddObjectName(Folder, nil, 0);
    end;
    if Depth > 0 then
      EnumItems(Folder, 0);
  finally

    Strings.EndUpdate;
  end;
end;

To learn more about the IShellFolder interface, click here.

Because we are working with Delphi, which affords us a friendly object framework, I have decided to encapsulate the functionality of the IShellFolder interface within a Delphi class. The TShellNode class makes use of the item list manipulation and several important shell functions. After having reviewed this article, it might do you well to examine the TShellNode class implementation in depth.

Below is a table describing the TShellNode class.

Properties Description
AbsoluteList A pointer to an absolute item list that identifies the node.
Count The number of nodes contained by the items property.
HasChildren A boolean value that indicates whether or not a node has child items.
Item An indexed property of child TShellNode objects.
Name A descriptive name of the node.
Path The file system path of the node.
Parent The parent node of the object. For the desktop node this property is nil.
RelativeList A pointer to a relative item list that identifies the node in the context of it's parent.
ShellFolder Provides direct access to the IShellFolder interface the node encapsulates.
Method Description
Create Creates a node either as a root, or an owned child node.
CreateFromList Creates a root node from an item list.
CreateFromFolder Creates a root node from a special folder location enumeration.
Destroy A virtual destructor that will release the node and all its children.
Assign Copies information from another node.
Path The file system path of the node.
Parent The parent node of the object. For the desktop node this property is nil.
Clear Frees the node's children contained by the item property.
Initialize A virtual method that can be used to initialize fields in derived classes.

The TShellNode class is designed to be a base class from which other more useful classes should be derived. Some of the properties and methods listed above are protected and might be made public in descendant classes. Derived classes should not redefine new constructors of alter the existing ones, but override the Initialize method instead.

Classes that extend the TShellNode class might add system image list index properties, searching capabilities, or information that ties the class closer to Windows resources. Really, the modifications are left to your imagination.

Before we move on to the sample application, there is one more undocumented Windows feature worth mentioning. Although the desktop is the root of the namespace, it is not entirely necessary to start namespace navigation from the root shell folder. Microsoft has defined a set of CoClasses that expose the IShellFolder interface. Listed in the table below are the names and descriptions of these component classes.

CoClass Description
CLSID_NetworkPlaces Network Neighborhood.
CLSID_NetworkDomain Network domain if installed.
CLSID_NetworkServer Network server group container if installed.
CLSID_NetworkShare Network shared drives container if installed.
CLSID_MyComputer My Computer.
CLSID_Internet My Network Places.
CLSID_ShellFSFolder The file system desktop folder.
CLSID_RecycleBin Recycle Bin.
CLSID_ControlPanel Control Panel.
CLSID_Printers Printers.
CLSID_MyDocuments My Documents.

For example, to you could create a simple printer selection combo box using the following code:

  EnumShellNamespace(ComboBox.Items, 1, CreateCOMObject(CLSID_Printers) as IShellFolder);

Sample application

The sample application builds on everything we have discussed so far. It derives a TShellTreeNode class from the previously mentioned TShellNode class, adding an ImageIndex and Strings property. The ImageIndex property refers to the node's image index in the system image list. The Strings property holds the display names for each item in the node's absolute item list. The program allows you to switch between absolute and relative item list views modes. Figure four below gives us a closer look at the item list tree as it appears in the sample program.

Although the sample application makes use of some of owner drawing, its main purpose is to demonstrate item list manipulation. Of particular interest is the GetItemListStrings procedure. This procedure demonstrates how to use the ILClone, ILFindChild, ILFree, ILGetCount, ILIsRoot, and ILRemoveLastID item list manipulation routines.

The source code to the sample application can be downloaded from a package at the end of this article.

Displaying a properties sheet

The shell namespace interfaces not only provides access to data structures, but user interface elements as well. For example, by using the IShellFolder.GetUIObjectOf method we can request a context menu. In the code that is to follow, we will be manipulating item lists to retrieve an IContextMenu interface. Using the IContextMenu we can select a verb, set a show state, and invoke a command.

A property sheet such as the one above can be invoked by passing the "My Computer" item list to the ShowProperties procedure.

procedure ShowProperties(Handle: HWND; ItemList: PItemIDList); overload;
var
  Desktop: IShellFolder;
  Folder: IShellFolder;
  ParentList: PItemIDList;
  RelativeList: PItemIDList;
  ContextMenu: IContextMenu;
  CommandInfo: TCMInvokeCommandInfo;
begin
  ParentList := ILClone(ItemList);
  if ParentList <> nil then

  try
    ILRemoveLastID(ParentList);
    OleCheck(SHGetDesktopFolder(Desktop));
    OleCheck(Desktop.BindToObject(ParentList, nil, IID_IShellFolder, Folder));
    RelativeList := ILFindChild(ParentList, ItemList);
    OleCheck(Folder.GetUIObjectOf(Handle, 1, RelativeList, IID_IContextMenu,
      nil, ContextMenu));
    FillChar(CommandInfo, SizeOf(TCMInvokeCommandInfo), #0);
    with CommandInfo do
    begin
      cbSize := SizeOf(TCMInvokeCommandInfo);
      hwnd := Handle;
      lpVerb := 'Properties';
      nShow := SW_SHOW;
    end;
    OleCheck(ContextMenu.InvokeCommand(CommandInfo));
  finally

    ILFree(ParentList);
  end;
end;
 
procedure ShowProperties(Handle: HWND; const DisplayName: string); overload;
var

  ItemList: PItemIDList;
begin
  ItemList := ILCreateFromPath(PChar(DisplayName));
  try
    ShowProperties(Handle, ItemList)
  finally
    ILFree(ItemList);
  end;
end;

The overloaded procedures above give the programmer access to a visual properties sheet through either a file system path or an item identifier list.

Other uses for items lists

The IShellFolder is not the only interface that can use item identifier lists. Shell links, shell extensions, shell icons, an shell views can all be used in conjunction with item lists to extend or plug into the Windows shell.

Microsoft Windows also provides a host of functions that use item lists. For example, with item lists you can retrieve the file system path of a special folders using the SHGetSpecialFolderLocation function. The SHGetPathFromIDList function can translate an item list into a file system path. The SHGetDataFromIDList function can query an item list for file system or network resource attributes. The SHGetFileInfo function is capable of so many diverse things that I have decided to delegate it's description to the API reference. I strongly urge you examine it.

Finally, since item lists are an addressing system for shell objects, they can provide use access shell objects commands. For example, you could copy files to the clipboard, empty the recycle bin, eject media, or launch a file search dialog, all by using item lists to invoke commands.

For more ideas about how you can use item lists and the shell namespace in your programs watch this screencast.

Conclusion

In this article we have uncovered some of the inner details of item lists. When have shown you their structure, how to create, manipulate, and dispose of them. We have shown you how to work with the shell's namespace, and provided an encapsulation of the shell hierarchy in a Delphi class. The structures and concepts exposed in this article can be leveraged in your own programs, and by applying this information you can use namespace to query network devices, access items in virtual folders, visually represent shell objects, and navigate the shell hierarchy. Using items lists opens your programs to the shell namespace and extends your programming toolbox to include all the command shell objects expose. There isn't any reason you can't begin to use item lists today.

Download Shell Namespace Examples

Posted on 05/05/2006


print send topic Rate this article  

Title:

image link indent align right align middle align left quote underline bold code quote
Comment:

page generated in 0.123 seconds | last modified 2/06/2011 8:35 PM
none  none