最后修改日期等。包含在任何其他文件夹下的项目可以是文件—一般使用其他的扩展特征集—但是也可能是完全不同的东西,如打印机或网络节点。
文件夹
文件夹是怎样实现的?文件夹实际上是一个Shell对象,它的行为被编码成一个COM模块,向Windows Shell暴露公共的接口。通过连接,文件夹可以告诉Shell怎样设计它的内容,使用什么样的图标显示,采用什么文字来描述,例如‘我的计算机’看起来像一个文件夹。他有一个代码层来感知PC上所有可用的驱动器,并且为每个驱动器附加一个子树到探测器的观察中。
每一种不同的文件夹都有不同类型的层次代码来提供不同的行为,对于文件型文件夹,行为就是扫描文件系统,恢复文件和子文件夹,并且通过列表控件显示它们。而打印机文件夹则记录所遇到的和所安装的打印机,并且为每一个都显示一个图标。你可以设计任何类型的具有任何行为的虚拟文件夹。文件型文件夹(即,目录)只 是其中的一种。
对非文件型文件夹,Shell的资料相对较少,仅在特殊文件夹中有些说明。事实上Windows Shell默认提供的是客户文件夹,他们与文件型文件夹的差别是: 可以包含文件和其他对象 能够提供对内容的不同观察 可以有选择地不被邦定到物理目录上
是系统定义的一部分,这部分由SDK提供特殊的函数集。
特殊文件夹列表可以在Win32 SDK资料和后面的第五章中找到。就像我原先说过的一样,特殊文件夹
是具有自己COM模块提供行为的特殊类型的文件夹。由于COM模块是新节点被加到Shell命名空间的前提,所以它就被称之为命名空间的扩展。
特殊文件夹使用户能够经过适当的接口访问系统信息。也就是说,在大多数情况下,这种文件夹与典
型的文件型文件夹提供的内容观察多多少少有些一致的地方。当然,精确的程度依赖于文件夹的类型。
与普通文件夹一样,特殊文件夹也可以包含文件。然而,通常是以稍微不同的方法表示,显示不同的特征。因此,特殊文件夹给文件赋予了不同的意义,并且,不把它们当作文件系统的正常实体(如果不是这样,它就不是特殊的了)。例如‘回收站’含有正常的而隐含的文件,因为这个文件夹要显示当前被标志为删除的文件列表,所以它把初始位置和删除日期特征显示在前面。
绝大多数(不是全部)特殊文件夹都依附于磁盘上的物理目录,正常情况下这是一个只读的目录,其内容就是所有需要以最适合的方法显示的信息。
换一个视角,绝大多数特殊文件夹都需要一个目录来存储它们的数据。这个目录可以被分配到磁盘的
任何地方,并且表示为文件夹和Shell支持的链接—这个特殊文件夹在命名空间中的位置。目录的内容不必显示为文件列表,相反,关联文件夹的代码可用最适合于它的角色的形式解释和显示它。
文件夹这个有着包含任何事物能力的东西导出两个重要概念:文件对象和PIDLs,这些我们将在后面章节中叙述。
文件对象
文件对象是一个包含在普通文件夹中的项—文件、记录、内存块、连接的设备等。‘文件夹项’、‘文件夹元素’和‘文件对象’这些术语是等价的。如果文件夹是一个文件型文件夹则文件对象就是文件。因此这里的‘文件’就比‘文件对象’稍微特殊一点,因为它精确地代表了文件系统中的一个实体。文件是一个文件对象,但 是,文件对象不一定是文件。
有一个敏感的问题出现在一般的文件夹和文件夹项的概念中,在Shell命名空间中我们怎样才能安全并唯一地区分出其中的项。如果Shell与文件系统一致(就像Windows 3.x一样),则文件的全名就能极好的保证这种唯一性。不可能有两个文件件具有相同的路径和名称。然而当文件夹比文件目录变得更普通的的时候,区分其中的项就需要更普通的方法了。
PIDLs
PIDL是一个数据结构,它是唯一地标识包含在文件夹中的项的一种方法。PIDL—标识符列表指针的缩写(pointer to an identifier list)—是一种比文件全名更通用的方法,它不仅在文件夹内而且在Shell的整个命名空间中保证了项的唯一性。更重要的是,它能透明地处理文件和文件对象。为了理解PIDLs的结构和作用,我们来分析一下它的二进制结构并与之所替代的路径名进行比较。
一个文件全名就是一个字符串,是一个具有非常特殊格式的字符串,是一些子串的串联,每一个子串都在文件系统的层次中表示一个层,有驱动器名,然后是目录名, 文件名,最后是扩展名,他们都由反斜线分隔。你所了解的文件全名就是指向这些相连元素的指针—此时指向的是一个字符串。从概念上讲,你可以把它看作是一个数组结构,其中的每一个元素都表示了一个路径名元素。
上图说明了路径名和PIDL的关系,同时他也给出了标识符列表在存储器中组织结构的概念。从编程的观点讲,PIDL是由LPITEMIDLIST类型实现的,它是ITEMIDLIST结构的指针。
typedef struct _ITEMIDLIST
{
SHITEMID mkid;
} ITEMIDLIST, *LPITEMIDLIST;
中间构成路径名各部分的对象映射到PIDL的项目标识符上。它们存在于整个SHITEMID结构中。
typedef struct _SHITEMID { USHORT cb; BYTE abID[1]; } SHITEMID, *LPSHITEMID;
结构的头两个字节指示项目标识符的尺寸—即,相关元素的数据以及用于表示的数据所占用的字节数。cb值必须包含它自身的尺寸。对应路径名,cb应该是所表示的驱动器或目录的长度加上一个unsigned short类型变量的长度。随后是这个结构数据的第一个字节。
一定要记住PIDL是一个‘平面’结构,不包含指针。形成PIDL的所有数据必须明显地聚集到一起,而不是通过指针连接。这就是说,我们不能使用典型的链表结构方案,使一个元素的最后成员指向链中的下一个元素。还有一点,就像图中所看到的,链表中下一个元素的地址可以通过cb相加到当前SHITEMID对象计算得出。这是设计规定的,因此要求相连的SHITEMIDs要连续分配空间。
定义PIDLs的构造规则是约定实现文件夹行为的代码。这些代码也应该确定使用什么样的数据来表示标识符的项。例如,假设想要实现一个文件夹,象文件系统那样显示Windows注册表,‘子文件夹’应该是注册表键‘文件对象’应该是注册表值。在这种文件夹中表示每一个元素的可能方法应该是使用相关的键名。这里我们能够看到PIDL是怎样使用与前面图中给出相同的方案格式的。注意HKEY_CLASSES_ROOT是一个长整型值,所以它有四个字节加两个字节的无符号短整数。
项目链表表示了路径踪迹,从命名空间的根到特定文件夹项。这个标识符链表聚集了链条上的所有元素,说明了一种通过Shell唯一地标识一个元素的方法。保证两个项目标识符在内存中连续分配是文件夹对象相关代码的职责。尽管路径名与PIDLs类似,他们并不等价,他们也不能交互使用,他们是不同的数据结构。
Shell观察
任何文件夹的内容都是通过一个对象调用Shell观察显示在Windows探测器中的。每一个文件夹都定义了他自己的Shell观察对象,并且所有相关于这个用户接口的任务都指派到这个对象上。对于文件型文件夹Shell观察对象是用列表观察控件实现,其中的项就是文件和子文件夹名。默认的Shell观察对象在他被调用处理文件时为每一个文件分配图标、显示名和类型名。
图标有几种方法确定,这依赖于请求文件的性质。一般使用自身定义的图标显示图标文件(.ico),而程序文件则显示其资源中定义的头一个图标。如果没有定义图标,则显示默认的。对于所有其他文件,Shell通常采用文件归属类所定义的图标。然而正象下面要揭示的那样,这个行为可以被客户化。
在整个Shell 环境中,文件都是根据文件扩展名指定的类型分组的,这种根据类型形成的文件集通常称之为文件类。它与一个图标和一个描述字符串相连,这个字符串显示在Windows探测器观察的详细信
息窗口上的类型列上。然而,要置换它们,指定的文件类就需要在注册表中注册,Shell将从那里读出类型信息和图标。
一旦定义了文件类,你就可以写代码来影响和修正Shell响应某些发生在特定文件类上事件的默认行为,这其中就包括绘制文件图标,弹出关联菜单,和显示属性对话框等。通过定义Shell扩展,你就可以动态地确定这些事件发生时要做些什么。例如,可以在关联菜单中加入新的项,和处理用户的点击,和动态地确定基于每个文件的图标显示。
钩住Shell
一般情况下,Shell扩展可以看作为钩子,他被设置在整个Shell中。Win32中,钩子是一段由应用定义的代码,一定事件发生时系统回调这段代码。有许多不同类型的钩子,他们的应用也非常广泛,有一些仅仅影响安装他们的应用程序,而另一些则影响所有系统中运行的应用。
这其中典型的例子就是键盘钩子,它能够使你在相应消息发送到应用窗口之前得到键盘按下的信息。其他钩子的例子如鼠标活动(移动,点击),窗口管理(建立,析构,活动),和消息处理。更多信息请参见Win32 SDK资料。
从程序员的观点看,钩子是一个具有固定的和预定义语法的回调函数,作为回调函数,系统基于已知的原形调用它。Shell扩展是COM接口,而不是回调函数,但是背后的原理是相同的,二者都允许你指定某些系统将要执行的代码来处理一些预定义的活动。
这一节特别注意到Windows的钩子。通过设置局部钩子,你仅仅能够捕获相关应用内发生的事件。但是设置全程钩子将会导致钩住任何运行的应用所发生的事件。设置全程钩子就是说,你的应用定义了一段代码,它可以被运行中的其他相关进程执行。事实上使用钩子完成Win32的跨进程边界和注入代码到其他进程地址空间是最容易的方法。它也是能在所有平台上工作的唯一方法。
Shell地址空间
注入代码到关联的另一个进程是重要的,因为,它允许你访问另一进程没有公开的对象,这对Shell编程尤其重要。当你成功地把代码插入到Shell地址空间后,你就可以查询Shell接口,改变用户接口,甚至置换‘开始’按钮。
全程钩子是一种使你的代码运行在Shell的地址空间中的方法,但是更有力和更灵活的机理是提供浏览器帮助对象—一种COM对象,探测器和IE在启动主窗口时自动加载的对象。
Shell内存分配器
在使用Shell时很快你就会接触到内存分配的问题,Shell提供了一个存储分配器,这个封装了要获得这个对象的引用,你应该使用SHGetMalloc()。它不是返回一个IMalloc接口的新指针—由IMalloc接口的服务可用来代替New或GlobalAlloc()。
CoGetmalloc()函数返回的—而是由系统Shell对IMalloc对象的一次引用。使用这个指针,你可以安全的释放由Shell分配的内存,并且使Shell释放这块内存。这可能有点陌生,但是在Shell编程中,这是个好习惯。
Shell任务条
任务条窗口作为Windows用户接口的一个已知的部件,仅仅是因为它包含了‘开始’按钮。然而我们之所以称之为‘Windows任务条’,是因为它实际上是一个窗口系列的特例,称之为‘应用桌面工具条’,最好的例证就是Office97的快捷方式杆。有一个特殊的函数和消息集与桌面工具条相关,然有趣的是仅有少量函数和消息影响到Windows的任务条。因此,即使资料没有明确地说明,系统任务条和桌面工具条仍然是不同的对象。
关于任务条的另一个错误观点是它包含了所有运行中应用的按钮,但是有两点原因说明这不是真的:
不是所有运行着的应用都显示在任务条上 作为按钮,任务条的唯一有的是‘开始’按钮 无论是否相信,作为按钮集出现的实际上是tab控件,只是具有特殊的类按钮风格罢了。
任务条起到了系统控制板的作用,使你能够访问所有运行中的应用。在很多情况下,我们希望能够限制任务条的功能—这是运行在公共PCs上应用的一个典型的需求,在那里你不希望用户能够运行其他程序或浏览文件系统。Win32 API并没有提供丰富的函数来操作任务条,但是,我们将试图在第九章中对此进行一些补救。
Shell API 函数
在与VC++6.0一起提供的MSDN库的Shell参考一节列出了100多函数,然而,其中的大多数都只涉及非常特殊的领域,有时感觉就象是Windows Shell的边界领域—这里所说的特殊是关于文件分析和屏幕保护的例程。
在这本书中,你不能找到关于每一个函数的详尽的说明,然而我们可以集中于文件和文件加操作的核心函数,并试图澄清他们含混不清的资料说明。为了有助于对其进一步分类,我们把它们分作五个不同的函数组。
在这本书中,你不能找到关于每一个函数的详尽的说明,然而我们可以集中于文件和文件加操作的核心函数,并试图澄清他们含混不清的资料说明。为了有助于对其进一步分类,我们把它们分作五个不同的函数组。
组 一般Windows函数 功能 涉及到屏幕保护,控制面板脚本程序,联机帮助,以及Shell拖拽(不是OLE拖拽) 访问探测器地址空间的函数,获得Shell存储分Shell内部函数 配器的函数,导出可执行程序的函数以及感觉用户接口改变的函数。 涉及到托盘域的函数和与Windows任务条通讯的 任务条函数 函数 操作文件的函数,他们执行如‘拷贝’,‘移动’,‘删除’和‘取得信息’等操作的系统活动,和添加文件到特殊的系统文件夹如‘最近文档’等。 操作文件夹的函数,使用这些函数,你可以浏览文件夹,获得系统文件夹的路径,发现文件夹的设置。 文件函数 文件夹函数
根据这个分组结构,可以看到有几个函数作为Shell编程接口的一部分并没有被显式引用,但是,他
们仍值得出现在这个表中。
组 功能
Windows Shell 编程



