操作Windows的namespace对象--如何使用对象标志符列表(PIDL)
Windows95在它的新外壳(Shell)中引入了一些新的对象,这些对象无法在文件系统中与之对应(例如,打印机文件夹及其内部的对象)。这些对象无法用"路径"来标志它们,因此Windows又引入了对象标志符(Item Identifier)和对象标志符列表(Item Identifier List)。每一个对象在它的所有者容器(container)对象(通常是一个文件夹)中有一个唯一的对象标志符(不同所有者的两个对象可能拥有相同的对象标志符),而一个对象是由一系列的标志符来唯一识别,这一系列的标志中包含最顶层的namespace的根对象--桌面(Desktop),以及所有的容器对象,最后是对象自己,这就是对象标志符列表。
一个对象标志符有一个很简单的数据结构(ShlObj.pas):
_SHITEMID = record
cb: Word; { ID的大小(包含cb在内) }
abID: array[0..0] of Byte; { 对象ID (可变长度) }
end;
abID部分的长度是可变的,它的内容是由管理这个对象的Shell扩展程序来定义的。
一个对象标志符列表的结构甚至更简单一些(ShlObj.pas):
_ITEMIDLIST = record
mkid: TSHItemID;
end;
一个对象标志符列表是由若干个对象标志符一个接一个形成的链式结构,最后以一个将cb域设为0的空的标志符来作为结束标志。对象标志符列表通常使用指针来操作,称为PIDL(Pointer to Item iDenfifier List)。PIDL使用IMalloc接口(interface)来分配空间和释放,这个接口可以通过调用SHGetMalloc()来获取。
这里有两种对象标志符列表,绝对列表和相对列表。绝对列表从namespace的根对象开始,而相对列表从对象的父容器对象开始。不幸的是:操作PIDL的API函数需要的是绝对列表,而接口(例如IShellFolder接口)的方法都是使用相对列表。
只有Windows 2000 API中公开提供了简单的转换PIDL的函数,在NT和W9x中却包含一大串未公开的函数(可以看看www.delphizine.com中的《The Secret World of PIDLs》文章)。
这里有一些Delphi函数来维护PIDL,这些函数是Microsoft sample C program, EnumDesk中部分函数的Pascal翻译版本:
// 分配一个新的PIDL,大小由cbSize指定,并将全部域初始化为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;
// 返回PIDL列表中的下一个PIDL
function NextPidl(Pidl: PItemIDList): PItemIDList;
var
lpMem: LPSTR;
begin
lpMem := LPSTR(Pidl);
lpMem := lpMem + Pidl.mkid.cb;
Result := PItemIDList(lpMem);
end;
// 返回PIDL的大小(一个列表可能含有一个或者多个IDL)
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;
// 连接Pidl1和Pidl2 -- 可以用于从相对列表创建绝对列表
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;
// 返回指定PIDL的一个新的副本,使用MAlloc接口
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;
在IShellFolder提供许多SHxxxxx函数使我们可以获取和操作PIDLs。
这里是其中部分最常用的函数(声明在ShlObj.pas),你可以查阅Windows SDK Help文档来获取它们的详细信息。
SHGetSpecialFolderLocation() 获取指定文件夹的PIDL,例如桌面(Desktop)、我的电脑(My Computer)等等。
SHGetPathFromIDList() 返回PIDL在文件系统中的路径(如果存在的话)。
SHChangeNotify() 通知外壳(Shell)应用程序执行了某项操作需要外壳更新自已(例如重命名一个文件夹)。
SHGetFileInfo() 返回大量有关namespace对象的信息:图标、类型、属性、显示名称等等,可用于返回系统ImageList的句柄(handle)。同时接收PIDL或者路径参数。
SHAddToRecentDocs() 将一个文档增加到外壳(Shell)的最近使用文档的列表中,或者清空这个列表。同时接收PIDL或者路径参数。
SHBrowseForFolder() 显示一个对话框让用户可以选择一个外壳(Shell)文件夹,并返回其PIDL。