delphi深度探索之pitemidlist的基本概念

author author     2023-03-09     660

关键词:

参考技术A

PIDL的秘密

  从Windows 开始 微软公司为操作系统引入了新的外壳界面 新的外壳从根本上改变了应用程序同操作系统的结合方式 遗憾的是微软公司对于发布同外壳相关的编程信息方面显得很吝啬 可以得到的资料非常少 而且质量也不高 对于Delphi开发者来说 情况就更为严重了 因为几乎所有的Windows API 文档都是针对C/C++程序员的 但是Nothing is impossible 在本文中 我们将开始外壳编程的历险 就让我们从PIDL开始吧

外壳命名空间

  新外壳系统中的一个核心概念就是命名空间(namespace) 对于DOS来说 命名空间可以理解为就是整个文件系统 它有着树一样的继承关系 它的树根被称为 根目录

  对于Windows x和NT来说 命名空间仍然是树状继承关系的 但它不再一一对应于文件系统了 文件系统变成了一个大的命名空间的一部分 新的命名空间发展了原有的文件夹和文件概念 新的文件夹仍然类似于旧的DOS目录 包含其他的命名空间元素 比如文件夹和外壳对象 而新的外壳对象同旧的DOS文件不同之处在于 所有的系统目录都是文件夹 但并不是所有的文件夹都是目录 所有的文件都是外壳对象 但不是所有的外壳对象都是文件

  新的命名空间的树根就是桌面文件夹 这从资源管理器左边的树视图中就能看到 桌面下包括我的电脑文件夹 其中包括了旧的DOS命名空间 磁盘驱动器 桌面和我的电脑明显不是文件系统的一部分 同样的特殊的文件夹 比如控制面板 打印机 回收站和网络邻居等等都不是原来意义上的文件系统了

  但不管外壳的概念如何变化 它必须是可唯一标识的 每个外壳中的文件夹和对象必须有一个唯一的 名字 名字 有两种类型 相对和绝对的 名字 相对 名字 是指相对一个给定的父对象 它是唯一的 比如我叫张三 我哥哥叫张大和张二 那么对于我的父亲来说 我的名字就可以唯一地确定我的身份了 但如何从全国所有名叫张三的同胞中找出我来呢 这就需要绝对的名字了 这时就应该用中国北京某胡同的张大胡子的儿子张三来唯一地确定我了 对于外壳对象来说 相对于根节点的路径就可以用来唯一确定它的绝对 名字

  对于老的DOS文件系统 每个文件都有一个唯一确定的路径名 这个路径名就相当于它的绝对名字 它的格式通常就是C:\\windows\\system\\…\\ 文件名 而单独的 样式的文件名字则是相对名

  对于新的Windows x系统 这种DOS方式的路径名已经不够用了 它无法描述控制面板这类外壳对象的名字 为此微软公司给出了两个新的数据结构 每个元素的相对名字用一个TShItemID记录来标识 当需要时我们可以合并这些记录 从概念上类似于用 \\ 连接DOS路径名 而一连串的这些记录就是项目标识符列表(IDL Item Identifier List) 在Delphi中使用TItemIDList来标识它 因为IDL主要是通过指针来进行操作的 因此通常主要使用的是它的指针形式PIDL 在Delphi中定义为PItemIDList PIDL就是在外壳命名空间确定唯一一个元素的通用方法 所有这些Delphi数据结构都定义在ShlObj单元中

  同DOS 样式的字符串类型的路径不同的是 PIDL是二进制类型的数据 同时TShItemID 和 TItemIDList 是变长的数据类型 其中TShItemID的定义如下

TShItemID = packed recordcb: Word; // 记录的大小 abID: array[ ] of Byte; // 外壳对象 ID数据 end;

  第一个记录成员是cb cb 中应该存放整个TShItemID记录的尺寸 而abID 被定义为只有一个元素的字节数组 但这并不意味着数组中只有一个元素 它可以扩展为cb个元素 另外TItemIDList 定义如下

TItemIDList = packed recordmkid: TShItemID;end;

  它只是有一个TShItemID类型的数据成员构成 需要注意的是这种定义方法意味着记录并不仅是一个TShItemID成员 而是一个TShItemID结构的列表 一个挨着一个 最后要使用一个cb为 的TshItemID标识列表的结束 下表中给出了一个TItemIDList的示意图 它由 个TShItemID 记录组成 注意cb 总是比abID的字节大 除了列表结束的标志记录的cb 这是因为cb 应该包含cb成员本身的字节大小 而它正好是

cb abID cb abID cb abID cb abID bytes bytes bytes bytes

  从表中就可以清楚地知道cb的用途了 它可以被用来作为可靠的路标来遍历一个TItemIDList PItemIDList指针指向TItemIDList记录的第一个字节 除非PItemIDList 为nil 否则列表中至少会有一个TShItemID 然后通过cb的值就可以知道列表中下一个TShItemID的起始位置 如果cb为 就表明列表结束了

  下面的代码用PItemIDList作为参数 然后遍历整个TItemIDList 并返回整个列表的尺寸 当需要复制列表时 获得的信息可以用来确定复制所需缓冲区大小

function GetPIDLSize(PIDL: PItemIDList): Integer;varCurrentID: PShItemID;begin// 判断PIDL是否为nil if (PIDL <> nil) thenbegin// 对于终止的标志的cb至少为 Result := SizeOf(CurrentID cb);// 初始化item id 指针并遍历列表直到碰到cb = 才终止// 把碰到的每个cb的值添加到结果中 CurrentID := PShItemID(PIDL);while (CurrentID cb <> ) do beginInc(Result CurrentID cb);Inc(PChar(CurrentID) CurrentID cb);endendelse // 如果PIDL为nil返回 Result := ;end;

  如同有相对和绝对路径一样 同样也有相对和绝对的PIDL 一个绝对的PIDL是从命名空间的根节点桌面开始算起的 而相对PIDL通常是从其直接父对象算起的

  外壳中的文件夹可以通过一个IShellFolder 接口来进行控制 这个接口提供了许多方法 这些方法的参数通常就是相对PIDL 因为接口本身就代表了父文件夹 而以Sh开头的Shell API函数通常则使用绝对PIDL作为参数 因为它们不是类 无法代表类 因此只能使用绝对PIDL 我们在应用中一定要搞清楚两者的区别

PIDL的内存分配

  在实际应用中 PIDL经常是在一个模块中被分配 而在另一个模块中被释放 比如外壳API经常会在函数内部分配并返回一个PIDL 这时我们的程序就要负责在使用后进行释放 这意味着内存的分配和释放必须是语言无关的 也就是说可以用C++写PIDL分配模块 而用Delphi写释放模块

  但实际上不同的开发语言的内存管理函数是完全不兼容的 如果使用Delphi的FreeMem 过程来释放一些C语言的Malloc函数分配的内存的话 产生的糟糕后果就是会破坏整个堆 为了解决这一问题 操作系统提供了外壳任务分配器(shell task allocator)来统一外壳内存管理 外壳任务分配器是通过IMalloc 接口实现的 IMalloc实现了一个非常完整的内存分配引擎 它定义在ActiveX单元中 获得一个IMalloc接口实例最简单的办法是使用SHGetMalloc API函数 这个函数定义在ShlObj 单元中 这些声明定义如下

IMalloc = interface(IUnknown)[ C ]function Alloc(cb: Longint): Pointer; stdcall;function Realloc(pv: Pointer; cb: Longint):Pointer; stdcall;procedure Free(pv: Pointer); stdcall;function GetSize(pv: Pointer): Longint; stdcall;function DidAlloc(pv: Pointer): Integer; stdcall;procedure HeapMinimize; stdcall;end;function SHGetMalloc(var ppMalloc: IMalloc):HResult; stdcall;

  下面是一个使用分配引擎的例子

varAllocator: IMalloc;Buffer: Pointer;begin// 获得IMalloc 接口 SHGetMalloc(Allocator); // 分配 个字节的缓冲区 Buffer := Allocator Alloc( );// 扩展缓冲区为 字节 Buffer := Allocator Realloc(Buffer ); //释放缓冲区 Allocator Free(Buffer);end;

  如果不需要IMalloc接口提供的全部功能 而只是想分配或释放内存的话 有两个未经公开的函数SHAlloc 和SHFree封装了对IMalloc接口的调用来分配和释放内存 它们在SHELL DLL中的索引分别为 和 当要想释放一个PIDL时 可以使用ILFree 这个未公开的函数 它的索引值为 三个函数的定义如下

function SHAlloc(BufferSize: ULONG): Pointer; stdcall;procedure SHFree(Buffer: Pointer); stdcall;procedure ILFree(Buffer: PItemIDList); stdcall;

路径和PIDL之间的相互转换

  如何将文件系统的路径转化为外壳形式的PIDL呢?微软公司的文档中记载的标准方式是先获得桌面的IShellFolder 接口 然后把要转化的路径名转化为PWideChar 类型的以null结尾的UNICODE字符串 然后作为参数调用桌面的IShellFolder接口的ParseDisplayName 方法才能获得PIDL 实际应用起来太复杂 不过不要紧 有三个未公开的函数可以帮助我们简化这一功能的实现

function SHILCreateFromPath(Path: Pointer;PIDL: PItemIDList; var Attributes: ULONG):HResult; stdcall;function ILCreateFromPath(Path: Pointer):PItemIDList; stdcall;function SHSimpleIDListFromPath(Path: Pointer):PItemIDList; stdcall;

  SHILCreateFromPath 函数实际上就是对桌面的IShellFolder接口的ParseDisplayName方法进行简单封装 而ILCreateFromPath函数则是对SHILCreateFromPath调用的简单封装 而SHSimpleIDListFromPath函数则实现了整个过程 它们的索引分别是 和

  其中SHSimpleIDListFromPath 相对要快一些 因为它并不校验路径参数的有效性 而SHILCreateFromPath 和ILCreateFromPath 在转化前都要校验路径的有效性 如果提供的路径是无效的 就会返回一个nil

  由于SHSimpleIDListFromPath 不校验路径 所以可以从任何路径获得一个PIDL而不会引起错误 但是有时这个函数返回的PIDL不完全正确 比如用它产生的PIDL来调用SHBrowseForFolder 函数显示浏览对话框的时候 偶尔结果显示的名字和图标是不正确的

  当想从一个绝对PIDL获得一个文件系统路径时 就相对简单多了 有一个公开的函数SHGetPathFromIDList可以实现这一功能 它定义在ShlObj单元中(有AnsiChar和widechar两个版本)

function SHGetPathFromIDList(PIDL: PItemIDList;Path: PAnsiChar): BOOL; stdcall;function SHGetPathFromIDListW(PIDL: PItemIDList;Path: PWideChar): BOOL; stdcall;

  注意 path参数对应的指针应该指向一个可以容纳MAX_PATH+ 个字符的缓冲区 以避免越界读写

显示名称

  如果想要获得一个PIDL对应的显示名称 文档中介绍的方法是使用IShellFolder接口的GetDisplayNameOf方法来完成 另外使用SHGetFileInfo API函数也能获得显示名 不过有一个未公开的API调用ILGetDisplayName函数使用起来是最方便的 它实际上就是调用桌面的IShellFolder接口的GetDisplayNameOf 方法 同时调用的标志值为SHGDN_FORPARSING ILGetDisplayName 函数的索引值为 不过这个函数不会返回通常的短显示名 而是返回包含了相应路径的长显示名 如果想得到的是短文件名的话 最好使用SHGetFileInfo函数 下面是函数的定义

function ILGetDisplayName(PIDL: PItemIDList;Name: Pointer): LongBool; stdcall; Windows NT和PWideChar

  回头看一下已经定义的未公开的函数就会发现通常字符串类型的变量 并没有定义为Pchar而是定义为Pointer 这是因为对于未公开的函数来说 在Windows x上字符串变量都是PAnsiChar类型的 而在NT上都是PWideChar类型的 没有办法像公开的函数那样可以任选ANSI或UNICODE版本的函数 未公开函数在Windows x上只能使用ANSI版本 在Windows NT 上只能使用UNICODE版本的函数 如果想在所有版本的操作系统上都能正常工作 就必须在运行时检查操作系统类型 SysUtils单元中的Win Platform 全局变量可以用来判断操作系统类型 如果程序是运行在Windows NT上的 在调用前就需要把字符串变量转化为PWideChar 类型 当函数返回时 又需要把返回字符串变回PAnsiChar 这种转化比较麻烦 但这就是使用未公开函数调用的代价

  如果想确定两个PIDL是否相同 标准方法是使用IShellFolder接口的CompareIDs 方法 相对的PIDLs 可以用他们父文件夹的IShellFolder接口 而绝对PIDLs的比较必须使用桌面的IShellFolder接口 同样的 系统也提供了未公开的快捷方法 要想确定两个PIDL是否相等 可以使用ILIsEqual 函数 如果想确定一个PIDL是否是另一个PIDL的子对象 可以使用ILIsParent 函数 如果希望判断子对象是否是父对象的最直接的子对象的话 需要设定函数的ImmediateParent 参数为True 下面的就是函数的定义

function ILIsEqual(PIDL : PItemIDList; PIDL : PItemIDList):LongBool; stdcall;function ILIsParent(PIDL : PItemIDList;PIDL : PItemIDList; ImmediateParent: LongBool):LongBool; stdcall;

  这两个函数的索引值分别为 和 要注意的是通过二进制的比较是无法判断两个PIDL是否相等的 因为相等的PIDL可能会有不同的二进制结构

解析PIDL

  有时 我们会想要分解一个PIDL为单独的ID列表 没有公开的函数可以实现这项功能 很显然 微软公司希望程序员自己实现切割PIDL的功能 幸运的是还是有未公开的函数可以简化开发

  如果我们想确定PIDL中所有标识符的尺寸 可以使用ILGetSize 函数 如果想遍历PIDL中每一个项目标识符的话 可以使用ILGetNext 函数 当给定一个PIDL后 函数会返回一个指向列表中下一个项目标识符的指针 如果PIDL为nil或已经指向了列表中的最后一项 函数会返回nil 要想返回列表中最后一项item identifier 可以使用未公开的ILFindLastID函数

  一个更专业的查找函数是ILFindChild 给定一个父PIDL和一个子PIDL 它将返回一个指向子PIDL独特部分的指针 比如 如果你把目录 C:\\DIR 的PIDL作为父PIDL 而把 C:\\DIR\\FILE TXT 的PIDL作为子PIDL的话 它会返回一个指针指向代表FILE TXT的子PIDL 如果给定的子PIDL不是父PIDL的子对象 函数返回nil 这些函数的索引值分别为 和 函数定义如下

function ILGetSize(PIDL: PItemIDList): UINT; stdcall;kfunction ILGetNext(PIDL: PItemIDList):PItemIDList; stdcall;function ILFindLastID(PIDL: PItemIDList):PItemIDList; stdcall;function ILFindChild(ParentPIDL: PItemIDList;ChildPIDL: PItemIDList): PItemIDList; stdcall;

复制和合并

  有时在进行外壳编程的时候需要制作一个PIDL的拷贝 给定一个已有的PIDL ILClone 函数将会分配并返回一个新的PIDL的克隆 而ILCloneFirst 函数可以从源PIDL中生成一个只包含第一个item identifier的PIDL 如果想获得最后一个item identifier的拷贝 组合使用ILFindLastID和ILCloneFirst函数调用就可以了 对于PIDL的其他部分 就需要不断调用ILGetNext和ILCloneFirst函数了 这两个函数定义如下 其索引值为 和

function ILClone(PIDL: PItemIDList): PItemIDList; stdcall;function ILCloneFirst(PIDL: PItemIDList):PItemIDList; stdcall;

  如果想合并两个PIDL 则可以使用ILCombine 函数 给定两个PIDL 它会创建一个包含两个源列表的新的PIDL 如果想把一个单独的item identifier同PIDL合并 可能需要使用ILAppendID 函数 它可以把一个TItemID 记录添加到一个已有的PIDL的开头或结尾 然而同ILCombine不同 原来的PIDL在操作后将被销毁 ILAppendID 函数中的PIDL参数甚至可以为nil 这两个函数的索引值分别为 和 函数定义如下

function ILCombine(PIDL : PItemIDList; PIDL : PItemIDList):PItemIDList; stdcall;function ILAppendID(PIDL: PItemIDList; ItemID: PShItemID;AddToEnd: LongBool): PItemIDList; stdcall;

全局内存克隆

  前面已经提到了 为PIDL分配内存需要使用外壳内存分配器 系统中有两个未公开的函数提供了不同的分配和释放内存的方法 它们是ILGlobalClone和ILGlobalFree 函数(索引值为 和 ) 函数定义如下

function ILGlobalClone(PIDL: PItemIDList):PItemIDList; stdcall;procedure ILGlobalFree(PIDL: PItemIDList); stdcall;

  在Windows NT中 这两个函数使用缺省进程的堆(由GetProcessHeap得到的) 堆的分配在某些方面比外壳分配器效率更高 而外壳在内部使用全局分配函数可以提高效率

  在Windows x 上外壳中的绝大多数内部结构都需要在DLL的所有实例 *** 享 同样PIDL使用的内存也应该是可共享的 ILGlobalClone 使用一个可共享的堆来分配PIDL的内存 使得可以从任何地方存取PIDL的指针

删改

  如果想删除整个PIDL 只要使用ILFree 函数就可以了 如果想从列表的末尾删除最后一个item identifier 可以使用ILRemoveLastID 函数

function ILRemoveLastID(PIDL: PItemIDList):LongBool; stdcall;

  它的索引值为 要注意的是它并不真的释放任何内存 它只是重置了列表的最后位置 它是唯一一个删除相关操作的函数 如果我们想从PIDL的开始删除一个item identifier 就只能使用ILGetNext 和ILClone 来生成一个从原始PIDL的第二个ID开始的拷贝了 然后使用ILFree删除源PIDL 从列表的中间删除一个ID显然更加麻烦了 但幸运的是在实际中几乎不存在这种需要

深入命名空间

  现在我们对PIDL已经有了一定程度的了解了 接下来就是研究如何遍历命名空间 桌面是遍历命名空间的根节点 从桌面开始 可以枚举外壳中的所有对象 在开始遍历命名空间前 需要获得桌面对象的IShellFolder接口 下面的代码演示了如何获得桌面接口

varDesktop: IShellFolder;BeginOleCheck(SHGetDesktopFolder(Desktop));

  IShellFolder 可以用来枚举外壳中的内容 设定或取得外壳对象的名字 查询它们的属性并通过界面元素进行交互 下面是一个使用IShellFolder 接口的例子

typeTItemListArray = array of PItemIDList; function GetShellItems(Folder: IShellFolder): TItemListArray;ConstSHCONTF_ALL=SHCONTF_FOLDERSorSHCONTF_NONFOLDERSorSHCONTF_INCLUDEHIDDEN;VarEnumList: IEnumIDList;NewItem: PItemIDList;Dummy: Cardinal;I: Integer;BeginResult := nil;I := ;if Folder EnumObjects( SHCONTF_ALL EnumList) = S_OK thenwhile EnumList Next( NewItem Dummy) = S_OK dobeginInc(I);SetLength(Result I);Result[I ] := NewItem;end;end;

  GetShellFolders 函数返回一组相对于父文件夹的PIDL列表 通过EnumObjects方法可以获得PIDL枚举接口 不过最终要负责释放全部结果中的项目  

function GetShellObjectName(Folder: IShellFolder;ItemList: PItemIDList): string;VarStrRet: TStrRet;BeginFolder GetDisplayNameOf(ItemList SHGDN_INFOLDER StrRet);case StrRet uType of STRRET_WSTR:BeginResult := WideCharToString(StrRet pOleStr);CoTaskMemFree(StrRet pOleStr);end;STRRET_OFFSET: Result := PChar(Cardinal(ItemList) + StrRet uOffset);STRRET_CSTR: Result := StrRet cStr;end;end;

  GetShellObjectName 函数则返回一个相对的PIDL的字符串表达 把这些代码集成起来 就可以编写一个过程来输出指定深度的外壳命名空间的层次关系了

procedure EnumShellNamespace(Strings: TStrings; Depth: Integer;Folder: IShellFolder = nil);procedure AddObjectName(Folder: IShellFolder; ItemList: PItemIDList; Level: Integer);VarS: string;BeginSetLength(S Level * );FillChar(PChar(S)^ Length(S) );Strings Add(S + GetShellObjectName(Folder ItemList));end;procedure EnumItems(Folder: IShellFolder; Level: Integer);varItems: TItemListArray;ItemList: PItemIDList;Flags: Cardinal;SubFolder: IShellFolder;I: Integer;BeginInc(Level);Items := GetShellItems(Folder);Tryfor I := to Length(Items) dobeginItemList := Items[I];AddObjectName(Folder ItemList Level);if Level < Depth thenbeginFlags := SFGAO_HASSUBFOLDER;OleCheck(Folder GetAttributesOf( ItemList Flags));if Flags and SFGAO_HASSUBFOLDER = SFGAO_HASSUBFOLDER thenBeginOleCheck(Folder BindToObject(ItemList nil IID_IShellFolder SubFolder));EnumItems(SubFolder Level);end;end;end;finallyfor I := to Length(Items) doILFree(Items[I]);end;beginStrings BeginUpdate;TryStrings Clear;if Folder = nil thenbeginOleCheck(SHGetDesktopFolder(Folder));AddObjectName(Folder nil );end;if Depth > thenEnumItems(Folder );FinallyStrings EndUpdate;end;end;end

  对于Delphi来说 由于其提供了一个非常友好的对象框架 所以这里对IShellFolder的功能进行了封装 实现了一个TShellNode 类 对TShellNode类进行了描述 AbsoluteList 标识节点的绝对PIDL指针Count Items属性中的节点数HasChildren 表示是否有子节点Item 子TShellNode对象Name 节点的显示名称Path 节点对应的系统文件路径Parent 对象的父节点 对桌面来说 这个属性为nilRelativeList 相对PIDL指针ShellFolder 对应于节点的IShellFolder接口Create 创建节点CreateFromList 根据PIDL创建节点CreateFromFolder 由特殊路径创建节点Destroy 析构函数Assign 复制节点信息Clear 释放节点的子对象Initialize 初始化方法

  TShellNode被设计成一个基类 可以从它继承更加有用的类来 一些在表 中列出的属性和方法是protected的 需要在继承类中声明为public 衍生类不应该重新定义constructors过程 但可以重载Initialize方法

  扩展TShellNode 的类可以添加系统图像列表索引属性 查找能力等等 这完全取决于你的想像力 还有一点是除了桌面外 微软公司还定义了一组CoClasses对象 它们都暴露了IShellFolder 接口 我们也可以从它们出发来遍历命名空间 下面列出这些CoClass的定义和描述 CLSID_NeorkPlaces 网络邻居CLSID_NeorkDomain 网络域CLSID_NeorkServer 网络服务器CLSID_NeorkShare 网络共享CLSID_MyComputer 我的电脑CLSID_Internet 我的网络CLSID_ShellFSFolder 文件系统的桌面目录CLSID_RecycleBin 回收站CLSID_ControlPanel 控制面板CLSID_Printers 打印机CLSID_MyDocuments 我的文档

  举例来说 可以使用下面代码来创建一个简单的打印机选择组合列表框

EnumShellNamespace(ComboBox Items

CreateObject(CLSID_Printers) as IShellFolder);  在例子程序中 我们从TShellNode类又衍生了一个TShellTreeNode 类 添加了图像索引和Strings属性 ImageIndex 属性对应于系统图像列表中的节点的图像索引 Strings 属性则保存着节点的绝对PIDL列表中每一项的显示名称 程序允许我们在绝对和相对PIDL察看模式间切换 下图就是程序中显示的外壳对象树的示意图

  例子程序的主要目的是演示如何进行PIDL的操作 在GetItemListStrings过程中 演示了如何使用ILClone ILFindChild ILFree ILGetCount ILIsRoot和ILRemoveLastID等例程

显示属性页

  IShellFolder接口不仅提供对外壳内部数据结构的存取 也可以调用界面元素进行交互 例如 使用IShellFolder GetUIObjectOf 方法 可以请求上下文相关菜单 在下面代码中演示了如何操作PIDL来获得IContextMenu 接口 并通过IContextMenu来调用菜单命令 比如显示属性页 调用我的电脑的属性命令显示属性页的示意图下图所示

procedure ShowProperties(Handle: HWND; ItemList: PItemIDList); overload;varDesktop: IShellFolder;Folder: IShellFolder;ParentList: PItemIDList;RelativeList: PItemIDList;ContextMenu: IContextMenu;CommandInfo: TCMInvokeCommandInfo;BeginParentList := ILClone(ItemList);if ParentList <> nil thentryILRemoveLastID(ParentList);OleCheck(SHGetDesktopFolder(Desktop));OleCheck(Desktop BindToObject(ParentList nil IID_IShellFolder Folder));RelativeList := ILFindChild(ParentList ItemList);OleCheck(Folder GetUIObjectOf(Handle RelativeList IID_IContextMenu nil ContextMenu));FillChar(CommandInfo SizeOf(TCMInvokeCommandInfo) # );with CommandInfo dobegincbSize := SizeOf(TCMInvokeCommandInfo);hwnd := Handle;lpVerb := Properties ;nShow := SW_SHOW;end;OleCheck(ContextMenu InvokeCommand(CommandInfo));FinallyILFree(ParentList);end;end;procedure ShowProperties(Handle: HWND; const DisplayName: string); overload;varItemList: PItemIDList;BeginItemList := ILCreateFromPath(PChar(DisplayName));TryShowProperties(Handle ItemList)FinallyILFree(ItemList);end;end;

PIDL的其他用途

  IShellFolder并不是使用PIDL的唯一接口 其他像文件快捷方式 外壳扩展等都利用PIDL来扩展或嵌入外壳 Windows还提供了一组公开的使用PIDL的函数 比如调用SHGetSpecialFolderLocation 函数就可以由PIDL获得特色文件夹的相应文件路径 而用SHGetDataFromIDList 函数可以查询文件系统或网络资源中的PIDL来获得相应属性

结论

lishixinzhi/Article/program/Delphi/201311/24786

delphi深度探索-codesite应用指南

参考技术A  Delphi虽然为我们提供极其强大的调试功能查找Bug仍然是一项艰巨的工作通常我们写代码和调试代码的所消耗的时间是大致相同的甚至有可能更多为了减少无谓的时间和精力的浪费有时我们还是需要专业调试工具的... 查看详情

ios底层探索之kvo—fbkvocontroller分析(代码片段)

...博客将分析一下FBKVOController这个优秀的KVO三方库。iOS底层探索之KVO(一)—KVO简介iOS底层探索之KVO(二)—KVO原理分析iOS底层探索之KVO(三)—自定义KVOiOS底层探索之KVO(四)—自定义KVOFBKVOController是一个函数式编 查看详情

android深度探索hal读书笔记8

看了本书第八章,我学习到了:蜂鸣器是开发板自带的一个硬件设备,控制蜂鸣器发声是通过向寄存器写入特定的值实现的。PWM驱动不同于LED驱动,其由多个文件组成,在编译时将这些文件进行联合编译。蜂鸣器也称为PWM(脉冲... 查看详情

深度探索c++关键字之virtual(代码片段)

    virtual在C++中有两个重要的用途:一是解决由多继承中父类有相同基类引起的子类中成员的二义性问题,二是实现多态。一、解决二义性1、引起二义性的原因    二义性是在多继承中出现的,如果派生类... 查看详情

c++的探索路17泛型程序设计与模板之基本形式(代码片段)

...的数据类型的程序设计方法。这种程序设计思想运用最为深度的内容就是STL(标准模板库),该部分也会在下一部分进行介绍。整体来说,泛型程序设计带来的好处与其所起的意义,丝毫不亚于面向对象的特... 查看详情

谁有深度学习书单和学习路线?

人工智能行业要大爆发了,想学一下机器学习技术。谁有深度学习书单和学习路线,可以分享一下吗?参考技术A推荐5本深度学习相关的书籍。1.《深度学习》(DeepLearning)出自Goodfellow、Bengio和Courville三位大牛之手的《深度学习... 查看详情

pascalhexagrammummysticum的深度探索

  PASCAL.Hexagrammum Mysticum.(六角迷魂图)。的深度探索。英中对比。英文蓝色,译文黑色,译者补充说明用紫红色(已校完,但尚未定稿,想再整理并补充内容)keyword:hexagon(六角形、六边形), 6点15边, 45对边交点(o... 查看详情

recyclerview探索之通过itemdecoration实现stickyheader效果

我在上一篇《小甜点,RecyclerView之ItemDecoration讲解及高级特性实践》讲解了ItemDecoration的基本用法及它的一些实践,抱着学习研究的态度,这一篇作为实践篇主要目的是尝试通过ItemDecoration来实现RecyclerView中的StickyHeader功能。关于... 查看详情

技术沙龙|teatalk带你深度探索sdn网络技术再创新

越来越多的企业、行业和政府机关顺应企业数字化转型、云服务和国家政策等趋势将业务迁移上云。随着移动云的快速发展,对网络提供差异化的服务能力也提出了很多新的考验。大规模数据中心、虚拟化SDN网络技术及超融... 查看详情

ios底层探索之多线程—gcd源码分析(信号量dispatch_semaphore_t)(代码片段)

...且对底层源码进行了分析,本篇博客将对信号量进行探索分析!iOS底层探索之多线程(一)—进程和线程iOS底层探索之多线程(二)—线程和锁iOS底层探索之多线程(三)—初识GCDiOS底层探索之多线程(四)—GCD的队列iOS底层探索之... 查看详情

delphi中基本控件之savedialog控件的使用总结(代码片段)

首先向Form窗体拖一个SaveDialog控件,Name属性改为:dlgSave,然后添加一个按钮,Caption属性改为:浏览,Name属性改为:btnBrowse。然后双击浏览按钮添加如下代码:procedureTfoffertool.btnbrowseClick(Sender:TObject);varfilePath:string;begindlgSave.Title:... 查看详情

android深度探索--hal与驱动开发第八章读后感

本章介绍蜂鸣器的实现原犁,并实现一个完整的蜂呜器驱动(可以打开和关闭蜂鸣器),蜂鸣器也称为PWM脉冲宽度调制,基本原理就是通过脉冲来控制蜂鸣器的打开和停止。蜂鸣器是开发板上带的一个硬件设备,可以通过向寄存... 查看详情

delphicodeeditor之基本操作

DelphiCodeEditor之基本操作毫无疑问,Delphi是高度可视化的。这是使用Delphi进行编程的最大好处之一。当然,任何一个有用的程序中都有大量手工编写的代码。当读者开始编写应用程序的UI部分后,可能会多花些事件来学习DelphiCodeEd... 查看详情

openharmony游戏开发探索之军棋翻翻棋实现(代码片段)

openharmony游戏开发探索之军棋翻翻棋实现一,引言大家也经常看到市面上有斗地主,麻将,飞行棋等不是很复杂的棋类游戏;然后作为没有开发过游戏的我,在思考一款游戏是如何开发的?于是就想在openharmony上尝试一下。最近... 查看详情

andriod深度探索(卷1)hal与驱动开发第八章读书心得

第八章介绍了第二个实验:蜂鸣器驱动,使开发板发出声音将介绍蜂鸣器的实现原理,并实现一个完整的蜂呜器驱动,通过该驱动可以控制蜂鸣器的打开与关闭。蜂鸣器也称为PWM脉冲宽度调制,基本原理就是通过脉冲来控制蜂鸣... 查看详情

delphi选择目录(代码片段)

functionselectdir:string;//如果取消取返回为空,否则返回选中的路径varInfo:TBrowseInfo;IDList:pItemIDList;Buffer:PChar;beginresult:=‘‘;Buffer:=StrAlloc(MAX_PATH);withInfodobeginhwndOwner:=GetActiveWindow;//少了这句,浏览目录选择框不会跳 查看详情

android深度探索

第八章   蜂鸣器开发板上带的一个硬件设备。可以通过向寄存器写入特定的值来控制蜂鸣器发出的声音。下来将介绍蜂鸣器的使用原理,并实现蜂鸣器的驱动。pwm驱动的使用不同于LED驱动的使用,其由多个文件组成,... 查看详情

android深度探索

第八章这章将介绍蜂鸣器的实现原犁,并实现一个完整的蜂呜器驱动(可以打开和关闭蜂鸣器).Linux驱动的代码重用,可以采用标准的C程序的方法将要重用的代码放在其他的文件中。如果要使用某些功能,include相应的头文件即... 查看详情