Title: Working with objects in the Windows' namespace.
Question: The W9x/NT4/2000 shell introduced a new way to identify and work with objects, beyond the file system
Answer:
With Windows 95 and its new shell were introduced some objects that have no counterpart in the file system (i.e the printer folder and its objects). These objects cannot be identified by their path, therefore were introduced the Item Identifier and the Item Identifier List. Every object has a unique item identifier within its container (usually a folder, two object in distinct containers may have identical item identifiers), and an object is uniquely identified within the namespace by the list of item identifiers from the root of the namespace - the Desktop - all the containers of the object, and the object itself.
An item identifier has a simple structure (ShlObj.pas):
_SHITEMID = record
cb: Word; { Size of the ID (including cb itself) }
abID: array[0..0] of Byte; { The item ID (variable length) }
end;
The abID part is variable length and its contents are defined by the shell extension responsible on managing the object.
An item identifier list is even simpler (ShlObj.pas):
_ITEMIDLIST = record
mkid: TSHItemID;
end;
An item id list is made of item ids one following the other, terminated by an item id whose cb field is set to 0. Item id lists are usually manipulated using a pointers to them, called PIDL (Pointer to Item iDenfifier List). PIDLs mys be allocated and freed using the IMalloc interface, which can be retrieved using SHGetMalloc().
There are two kind of lists, absolute and relative. Absolute lists start from the namespace root, relative lists start from the parent container. The bad news: API function using PIDLs
require absolute lists, interface methods (i.e. IShellFolder) require relative lists.
Only Windows 2000 API has documented functions to convert PIDLs easily. NT and W9x have a bunch of undocumented functions (look for 'The Secret World of PIDLs' in www.delphizine.com).
Here are some Delphi function to manipulate PIDLs. They are tha Pascal translation of functions in a Microsoft sample C program, EnumDesk:
// Allocates a new PIDL of cbSize bytes, and initialize all fields to 0
function CreatePidl(cbSize: UINT): PItemIDList;
var
lpMalloc: IMalloc;
begin
if SHGetMalloc(lpMalloc) NOERROR then
begin
Result := nil;
Exit;
end;
Result := lpMAlloc.Alloc(cbSize);
if Result nil then
FillChar(Result^, cbSize, #0);
end;
// Returns the Next PIDL in PIDL list
function NextPidl(Pidl: PItemIDList): PItemIDList;
var
lpMem: LPSTR;
begin
lpMem := LPSTR(Pidl);
lpMem := lpMem + Pidl.mkid.cb;
Result := PItemIDList(lpMem);
end;
// Returns PIDL size (a list may contain one or more IDLs)
function GetPidlSize(Pidl: PItemIDList): UINT;
begin
Result := 0;
if Pidl nil then
begin
Result := SizeOf(Pidl.mkid.cb);
while Pidl.mkid.cb 0 do
begin
Result := Result + Pidl.mkid.cb;
Pidl := NextPidl(Pidl);
end;
end;
end;
// Concatenates Pidl1 and Pidl2 - can be used to build an absolute
//list from relative ones
function ConcatPidls(Pidl1, Pidl2: PItemIDList): PItemIDList;
var
cb1, cb2: UINT;
begin
if Pidl1 nil then // Pidl1 may be nil...
cb1 := GetPidlSize(Pidl1) - SizeOf(Pidl1.mkid.cb)
else
cb1 := 0;
cb2 := GetPidlSize(Pidl2);
Result := CreatePidl(cb1 + cb2);
if Result nil then
begin
if Pidl1 nil then
Move(Pidl1^, Result^, cb1);
Move(Pidl2^, (LPSTR(Result)+ cb1)^, cb2);
end;
end;
// Returns a new PIDL which is a copy of the passed PIDL, using the
// task allocator
function CopyITEMID(Pidl: PItemIDList; MAlloc: IMAlloc): PItemIDList;
begin
Result := MAlloc.Alloc(Pidl.mkid.cb + SizeOf(Pidl.mkid.cb));
Move(Pidl^, Result^, Pidl.mkid.cb + SizeOf(Pidl.mkid.cb));
end;
We can retrieve and work on PIDLs using the SHxxxxx functions and the IShellFolder interface.
Here are some of the most useful functions (they are declared in ShlObj.pas), look in the Windows SDK help file for details of how they are used:
SHGetSpecialFolderLocation() retrieves the PIDLs of a special folder i.e. the Desktop, My Computer and so on.
SHGetPathFromIDList() returns the file system path, if exists, of a PIDL.
SHChangeNotify() notify the shell that an application performed an action that requires the shell to update itself (i.e. renamed a folder).
SHGetFileInfo() returns many information about any namespace object: icons, type, attributes, display names and more. May be used to retrieve the handle of the system image list. Accepts both PIDLs and paths.
SHAddToRecentDocs() adds a document to the shell's list of recently used documents or clears all documents from the list. Accepts both PIDLs and paths.
SHBrowseForFolder() displays a dialog box that enables the user to select a shell folder, and returns its PIDL.