第 6 章 可连接对象

60
6 6 第 第第第第第 第 第第第第第 1. 第第第第第第第 第第第 第第第第第第第第第第第第 第第第 第第第第第第第第第第第第第第第第第 第第第第第第 :, 第第第第第第第第 第第第第第第 第第第第第第第第第 第第第第第第第 ,, 第第第第第第第第第第第第第 第第第第第第第第第第第第第第第第第 ,。 第第第第第 第第第第第第第 第第第第第第第第第第第第第第第 :, (sink) 第第第第第 COM 第第 第第第第第第第第第第第第第第第第第第第 ,。

description

第 6 章 可连接对象. 1. 入接口和出接口 入接口:客户完成对象的创建和释放 出接口:对拥有出接口的对象称为可连接对象,也称为源对象 这些出接口并不是 由对象来实现,而是由客户来实现,客户实现这些接口,并把这些接口指针告诉对象。以后对象利用这些接口指针与客户进行通信。 接收器:在客户程序方,实现这些接口的对象被称为接收器 (sink) ,它本身就是一个 COM 对象,只用来监听并处理组件对象的通知或请求。. 6.1 可连接对象结构模型. 1. 客户与可连接对象之间的通信是双向的 (1) 客户调用对象所提供的服务,对象提供相应的服务给客户。 - PowerPoint PPT Presentation

Transcript of 第 6 章 可连接对象

Page 1: 第 6 章 可连接对象

第第 66 章 可连接对象章 可连接对象 1. 入接口和出接口 入接口:客户完成对象的创建和释放 出接口:对拥有出接口的对象称为可连接对象,

也称为源对象 这些出接口并不是 由对象来实现,而是由客户来实现,客户实现这些接口,并把这些接口指针告诉对象。以后对象利用这些接口指针与客户进行通信。

接收器:在客户程序方,实现这些接口的对象被称为接收器 (sink) ,它本身就是一个 COM对象,只用来监听并处理组件对象的通知或请求。

Page 2: 第 6 章 可连接对象

6.1 6.1 可连接对象结构模型可连接对象结构模型 1. 客户与可连接对象之间的通信是双向的 (1) 客户调用对象所提供的服务,对象提供相应的服务

给客户。 (2) 对象可以通过它的出接口向客户发出请求,或者 给

客户发送通知或事件。 6.1.1. 客户与可连接对象的关系 客户程序把接收器的接口指针通过参数传递给连接对

象,可连接对象记录下接收器的接口指针,以后便可通过此接口指针调用接收器的成员函数。

接收器也是一个 COM 对象,但是它在客户端实现,不需要 COM 来创建,不需要 CLSID 和类厂,单独的 COM 对象,自己的引用计数,以及接口查询。

Page 3: 第 6 章 可连接对象

客户与可连接对象的关系客户与可连接对象的关系 客户与可连接对象之间的结构 (1) 因为可连接对象本身是一个 COM 对象,应用引用计数,因此

一个接收器可以被多个可连接对象使用。 (2) 每个可连接对象也可以连接多个接收器。 可连接对象和接收器之间可以形成一对多和多对一的关系。

Page 4: 第 6 章 可连接对象

6.1.2 6.1.2 可连接对象的基本结构可连接对象的基本结构 在可连接对象中,通过接口 IConnectionPointC

ontainer 管理所有的出接口。对应于每一个出接口,可连接对象又管理一个连接点 (connection point) 的对象,每一个连接点对象实现了 IConnectionPoint 接口,客户通过连接点对象建立接收器与可连接对象的连接,

连接点既可以访问可连接对象的内部信息,也可以访问客户方的接收器,,而且连接点对象的引用计数可以包含在可连接对象的引用计数器内部,可以直接使用可连接对象的引用计数器。

Page 5: 第 6 章 可连接对象

基本结构基本结构 一个可连接对象可以支持多个出接口,在接口

IConnectionPointContainer 的成员函数中使用一个枚举器暴露出此对象所支持的所有出接口

对于每一个出接口的连接点对象,在接口 IConnectionPoint 中使用一个枚举器管理它所连接的接收器。

Page 6: 第 6 章 可连接对象

6.2 6.2 实现可连接对象实现可连接对象 6.2.1 枚举器 利用枚举器可以很方便的访问一组数据单元。客户程

序利用枚举器对 COM 对象中的数据单元进行枚举操作,枚举器把客户对数据单元的操作进行了标准化,因此 ,COM 对象可以按照标准的方法把数据提供给客户。

对枚举的数据单元可以进行 Next 、 Skip 、 Reset 和 Clone 操作

枚举器所枚举的数据单元不确定 , 因此使用模板 <ELT_T>,String—IEnumString,IUnknown-IEnumUnknown

枚举器对象是一个内部对象 , 只暴露枚举接口 , 不需要 CLSID 和类厂 .

Page 7: 第 6 章 可连接对象

源对象源对象 6.2.2 源对象 源对象通过 IConnectionPointContainer 接口暴露自己的

出接口信息,只要 COM 对象支持出接口,则必须实现此接口。

此接口实现了两个函数 Eunm 和 Find 。 Enum 用来返回连接点枚举器,以便客户利用此枚举器访问 COM 对象的所有连接点。 Find 函数根据给定的 IID ,返回相应出接口的连接点。

其中 Enum 是一个典型的枚举器接口,枚举的数据成员为连接点对象,相应的数据类型为 IConnextionPiont 接口。

Page 8: 第 6 章 可连接对象

连接点连接点 连接点实现了 IConnectionPoint 接口 , 此接口有

五个函数 GetConnectionInterface- 返回对应出接口的 IID GetConnectionPointContainer- 返回源对象的此

接口指针 Advise- 建立接收器和源对象之间的连接 Unadvise- 取消连接 EnumConnections- 访问建立的连接 , 返回连接

枚举器对象

Page 9: 第 6 章 可连接对象

实现可连接对象实现可连接对象 6.2.4 建立连接过程 假设源对象支持出接口 ISome ,客户方创建了一个接收器对象,

它实现了接口 ISome ,接口指针为 pSome 。以下是客户如何从一个基本的 IUnknown 接口指针 pUnk 建立连接的过程步骤。

(1) 调用 pUnk->QueryInterface(IID_ICPC, &pCPC) ,如果不成功,则不支持出接口。

(2) 调用 pCPC->Find(IID_ISome,pCP) ,如果不成功,则表明不支持出接口 ISome 。调用 pCPC->release() 。

(3) 调用 pCP->Advise(pSome,&dwCookie) 建立与接收器的连接。客户保存连接标识 dwCookie ,以后断开连接时需要用到 dwCookie标识连接。

(4) 当客户要取消连接时,调用 pCP->Unadvise(dwCookie) ,并调用 pCPC->release 释放连接点对象。

连接点对象与原对象分开实现,可以保持各自的独立性,原对象的变化不影响连接点,连接点也不影响原对象,当然连接点对象要受到原对象的控制。

Page 10: 第 6 章 可连接对象

接收器实现接收器实现使用 New 创建使用 Release 释放

Page 11: 第 6 章 可连接对象

事件的激发和处理事件的激发和处理入函数 ( 属性修改 )用户操作 ( 鼠标 )其它对象和客户调用调用封装函数完成

Page 12: 第 6 章 可连接对象

类型信息类型信息动态实现接收器使用 IDispatch 作为出接口

Page 13: 第 6 章 可连接对象

IDispatchIDispatch 接口作为出接口接口作为出接口 用标准的 IDispatch 接口作为出接口:利用此接口中方法的分发功

能实现事件控制函数。此接口是自动化对象的基本接口。 此接口的特点: (1) 此接口支持迟绑定。 (2) 自动化对象的这个接口的 vtable 是固定的 ( 不支持指针的语

言 )

(3) 用名字访问属性和方法非常简单容易。

Page 14: 第 6 章 可连接对象

第第 77 章 结构化存储章 结构化存储 结构化存储也称为永久化存储机制。 COM 针

对组件软件的需要,在文件系统的基础上,提出了结构化存储。利用结构化存储,组件程序之间可以更好地协同工作,一个组件程序可以与另一个组件程序共享同一个文件。如同两个应用程序共享同一文件一样。

COM 调用结构化存储的规范,包括一组接口和实现这些接口成员函数地一些规则。

用复合文档技术来实现 COM 的结构化存储。此技术是 OLE 地基础, OLE最初地目的是在文档中嵌入和链接对象。

Page 15: 第 6 章 可连接对象

7.1 7.1 结构化存储基础结构化存储基础 结构化存储“借用”文件系统地概念,在文件内部构造了一个类似于文件系统地树状层次结构,层次结构地节点可以是两种对象:存储对象和流对象,每个存储对象或流对象都是一个可以独立进行读写操作地对象,组件程序只对它拥有地节点对象进行操作。从应用系统整体上看,这些组件程序在共享同一文件。这个层次结构就构成了结构化存储的基本概念。

7.1.1 从文件系统发展到结构化存储 操作系统提供了流式存储结构,目录是文件的容器,

文件是叶节点,保存数据对象,大文件可以不连续。

Page 16: 第 6 章 可连接对象

结构化存储基础结构化存储基础 结构化存储的引入使多个组件程序共享

访问同一个文件成为可能,而且每一个组件程序可以有自己独立的存储空间。否则客户程序就需要进行统一管理文件,当需要进行读写操作时,客户程序吧文件句柄传递给组件程序,由组件程序完成实际的文件操作,然而客户程序并没有能力保证一个组件程序不去干扰其他的存储空间。

Page 17: 第 6 章 可连接对象

结构化存储的核心思想: 在一个文件内部构造一个树状层次结构,有如

文件系统的层次结构一样,在文件内部的树状结构中,每个节点可以是两种对象之一 : 存储对象和流对象,根结点为根存储 (root storage) ,根存储下面可以有子存储对象或流对象,在子存储对象下面可以再有子存储和流对象,从而形成层次树状结构。存储对象本身不包含数据信息,它只是作为流对象或子存储的容器,而流对象可以包含任意长度的数据信息。经常称此存储结构为文件内部的文件系统,把这样的文件称为复合文档。

Page 18: 第 6 章 可连接对象

每个组件程序只访问属于自己的存储对象或流对象。

客户程序负责根存储的访问操作,它可以把根存储操作封装到某个组件程序中。如图 7.3 所示:

利用结构化存储自动实现了“部分访问”和“增量访问”的特性。只需要访问必要的信息、只保存修改过的数据信息。

Page 19: 第 6 章 可连接对象

结构化存储基础结构化存储基础7.1.2 存储对象和流对象 对一个完整的存储操作来说,它被分为

两个层次: 应用程序调用 API 函数 操作系统提供 API 函数的实现 在结构化存储上, COM库提供了结构化

存储的实现,它提供了一组接口和 API函数供组件程序调用,组件程序利用这些接口和函数完成实际的存储操作。因此,结构化存储中的存储对象和流对象由 COM库来实现。

Page 20: 第 6 章 可连接对象

流对象:流对象非常类似于单独的磁盘文件,它也是进行读写操作的基本对象,利用流对象可以保存各种类型的数据,它有自身的访问权限和搜索指针。流对象提供了连续的字节流存储空间,虽然实际的存储位置在文件不一定连续,但 COM 封装了内部的数据结构,提供了连续的字节流抽象结构。

流对象也用一个字符串作为其名称。同一个存储对象下的流对象不能重名。

流对象是一个由 COM 实现的组件对象,它实现了基本的 COM 接口 IStream ,应用程序通过 IStream 接口访问流对象,进行各种数据访问操作。

Page 21: 第 6 章 可连接对象

结构化存储基础结构化存储基础在实现组件程序和应用程序时,只通过 I

stream 接口与流对象打交道。其中的函数的说明如表 7.1

Read , write , seek , copyto , commit ,revert , lockregion , unlockregion , stat ,clone

其中的 SetSize 可用于程序性能的优化,允许客户程序预先分配好流对象的空间,有助于提前发现程序中的问题,以及提高程序的性能。

Page 22: 第 6 章 可连接对象

存储对象类似于目录对象,也有一个字符串名称,但不存储数据。存储对象暴露 IStorage 接口,客户通过 IStorage 接口对存储对象进行操作, IStorage 接口的成员函数与平时进行的目录操作相似。比如:列出目录中所有的子目录和文件、移动、拷贝、删除、新建文件和目录等。

存储对象作为容器对象,提供了针对其子对象的各种操作,如 CreateStream 、 CreateStorage 、EnumElements 、 RenameElement等,也提供了用于事务处理的 Commit 、 Revert 成员函数以及其它一些操作。说明见表 7.2

Page 23: 第 6 章 可连接对象

结构化存储基础结构化存储基础 其中 IStorage::CreateStream 和 IStorage::OpenStr

eam 成员函数是获取流对象的唯一途径。 在调用 IStorage::Stat( 用存储对象的一些有用信

息填充 STATSTG 结构,包括名字和 CLSID)函数时必须注意,如果 flags 参数为 STATFLAG_DEFAULT值,那么 COM 为存储对象名字分配内存,因此必须用与此一致的方法来对其释放,在此可以调用 CoTaskMemFree辅助函数释放字符串资源,如果只想要存储对象的大小和类型,而不是名字信息,那么可以把此参数设置为 STATFLAG_NONAME值,这时没有分配内存,因此就不需要释放。

Page 24: 第 6 章 可连接对象

从 IStorage 的接口成员函数可以看出,对于存储对象,处理方式可以按照统一的方式进行

在 CopyTO 和 MoveElementTo 这两个成员函数中,参数 pStgDest 指定的目标存储对象既可以位于同一树状结构,也可以在另一个复合文件中。

只能从 CreateStorage 和 OpenStorage 两个成员函数得到一个子存储对象。 COM库提供相应的函数或者其它途径创建或打开根存储对象。

Page 25: 第 6 章 可连接对象

用结构化存储设计应用用结构化存储设计应用 用一个例子来进行说明: 一个有很多章组成的文档,其中每一章内又有

很多节。文档很大,因此程序启动时,把整个内容都读入内存效率较低,只是希望能够插入和删除文档的一部分,这时可以设计一个复合文档,根存储包含一些子存储,子存储代表章,每一章的子存储又包含一些流,它们代表节,在节存储下有一些流对象和存储对象,分别用来保存文本信息和图片、表格信息。结构如图7.5 。

Page 26: 第 6 章 可连接对象

这样的结构信息可以用一些交叉引用的指针进行管理,文档维护一组指向每一章的内存指针,在章结构中维护一组指向节的内存指针,节的结构中包含了实际的文字信息。如果文档包含图片或表格之类的格式化信息,则在节的结构信息中也要包含这些结构信息。在内存中,可以使用指针来构造文档结构,而且,当增加或删除章节信息时,只需要通过指针操作维护相应的结构既可。

如果存储到普通的文件中,则指针就不适用了,一般采用偏移量以便正确定位信息结构,比如,文档信息结构保存了每一章的偏移量,章的结构信息保存了每一节的偏移量。如图 7.4 。

Page 27: 第 6 章 可连接对象

用结构化存储设计应用用结构化存储设计应用 用普通文件来组织很难管理,增加信息或修改信息,都要设计整个文档,因此既影响了程序的性能,也使应用程序实现起来更加复杂。

复合文档结构可以很好的解决部分信息的编辑和管理问题。如果增加或删除某些章节,不会影响其它的章节。同时, COM 可以自动管理文件的空闲空间,而不需要应用程序管理磁盘空间的申请和回收。

优点:章节对象被放置在与主应用程序分离的组件程序中,组件程序可以直接把章节信息保存到磁盘中,而不需要通过主应用程序,而且组件程序只需要保存修改过的信息。

Page 28: 第 6 章 可连接对象

结构化存储特性结构化存储特性 7.2.1 访问模式 分两种基本的访问模式:直接访问和事务访问 直接访问模式下:操作马上生效 事务访问模式下:所做的操作被缓存起来,只

有当提交 (Commit) 时才真正有效,如果调用 Revert 函数,则可恢复到上一次提交或刚打开时的状态。在 IStorage 的前四个成员函数中,有一个 gfMode 参数指定了在存储对象或者流对象的访问模式,此参数可以由一些预定义的常量组合而成,按其功能分为以下几组:

Page 29: 第 6 章 可连接对象

(1) 创建存储对象或者流对象的标志常量: STGM_CREATE : ------ 创建新的存储对象,若存在删除-创建新的

STGM_CONVERT :--允许创建操作保留原来的数据,并把这些数据保存在名为“ CONTENTS” 的流对象中,并且该流对象总是位于当前存储对象下。此标志只适用于存储对象的创建操作。

STGM_FAILIFRHERE :如果同名对象存在,则创建失败。适用于存储对象和流对象

(2) 创建临时存储标志常量 STGM_DELETEONRELEASE :根存储对象释

放时,删除此文件

Page 30: 第 6 章 可连接对象

访问模式访问模式 (3) 直接模式和事务模式标志常量 STGM_DIRECT :直接 STGM_TRANSACTED :事务 (4) 优先标志常量 STGM_PRIORITY :打开存储对象之前先读

到一些流信息 ,必须和 STGM_DIRECT 和 STGM_READ 一起指定。

(5) 读写操作标志常量 STGM_READ :指定读权限 STGM_WRITE :指定写权限 父对象的读写权限也限定了子对象的读写权限

Page 31: 第 6 章 可连接对象

(6) 共享权限标志常量 STGM_READWRITE :可读性权限 STGM_SHARE_DENY_READ :禁止再以读

方式打开 STGM_SHARE_DENY_WRITE :禁止再以写

方式打开 STGM_SHARE_EXCLUSIVE :是上面两个的

组合 STGM_SHARE_DENY_NONE :不禁止 访问模式标志比较复杂,应尽量按照规则给出

标志组合。

Page 32: 第 6 章 可连接对象

事务机制事务机制 7.2.2 事务机制 事务特性可以篏套使用: A 包含 B , B 包含 C ,如都

使用 STGM_TRANSACTED 标志,那么对对象 C 作了修改,在调用 Commit 函数后,只有 B 知道, A 是不知道的,对 B 调用 Commit 函数后, A才知道 C 被修改了。 如果 B 用 STGM_DIRECT 标志,则 A就知道C 的修改。如果 A 用 STGM_DIRECT 标志,则对 C 的修改直接写到磁盘文件中。

如果是事务模式,则 COM 实际上在内存或临时文件中保存了一份拷贝,所作的操作就是在此拷贝中进行,只有调用 Commit 时, COM 把此拷贝才提交。 STGM_PRIORITY 标志只能与 STGM_DIRECT 标志一起使用。

Page 33: 第 6 章 可连接对象

Commit 成员函数中有一个参数 grfCommitFlags用于控制提交的方式:

STGC_DEFAULT :新的代替旧的数据 STGC_OVERWRITE :新的代旧的,对内存要

求小 STGC_ONLYIFCURRENT :共享情况下,阻止

其它用户执行提交 STGC_DAN :提交并不马上写到磁盘文件中 Release 释放之前没有提交,相当于 Revert

Page 34: 第 6 章 可连接对象

命名规则命名规则存储对象和流对象的命名规则与文件系

统的命名规则不同,其如下: 首先,根存储对象的名字实际上就是复

合文件的文件名。 其次,子对象的命名规则遵循 COM 给出

的约定,其如下: (1) 长度不超过 32 个字符 (2) 不能使用字符 \ / : !

(3) 名字 . 和 .. 被保留

Page 35: 第 6 章 可连接对象

(4) 名字保留大小写,但比较文件名时大小写不敏感。 (5) 首字符使用大于 32 的字符,小于 32 的有特殊意义 小于 32 的名字有特殊意义, COM 利用首字符值,把

对象分成了多个名字空间,只有特殊的代码才能使用这些名字空间:

(1) ‘0x01’ 和’ 0x02’ 的名字为 COM 以及 OLE专用 (2)‘0x03’ 被管理父对象的客户代码使用,客户程序可保

存一些说明信息,比如标志信息、摘要信息 (3)‘0x04’ 为结构化存储本身使用 (4)0x05~0x1f 保留为将来使用

Page 36: 第 6 章 可连接对象

增量访问增量访问增量访问有两个意义: (1) 保存和打开文件时减少操作时间。装入时,

只要装入需要编辑的部分,或者产生时只装载简单的摘要信息,待编辑到某处时再装入相应的信息。

(2) 降低了应用程序对系统资源的要求,只需要能够装下需要编辑的某一部分就可以了。

从根存储出发,依次调用 IStorage 的前四个成员函数来进行定位,定位到流,进行编辑修改,时存储对象,可以创建、删除子对象等。

Page 37: 第 6 章 可连接对象

但有一个问题就是“文件碎片”:也就是空间的回管理问题。对复合文档进行修改和删除操作,复合文档的尺寸总是再增长。因为删除了对象时, COM 只是把这些对象所占用的磁盘空间作了“未用”的标记,而不是释放这些磁盘空间,当然 COM 以后会重用这些空间,但在重用之前,这些空间仍保留在文件中。

解决办法:首先创建一个新的复合文档,然后调用原先根存储对象的 CopyTo 函数,把以前的树结构复制到新的根存储中,则新的复合文件没有碎片空间。

Page 38: 第 6 章 可连接对象

7.37.3 结构化存储实现:复合文结构化存储实现:复合文档档

LockBytes 对象和 ILockBytes 接口 在特定的操作系统平台上实现结构化存储,关键在于两个方面:

(1) 如何把根存储与底层存储介质结合起来 (2) 实现存储对象和流对象 复合文档通过一个被称为“ LockBytes” 的对象,解决了第一个问题。

LockBytes 对象实际上是所有存储介质的一种抽象表达方式,它把存储介质描述成一般化的字节序列,不管是磁盘文件还是内存区域都可以按字节序列对待。它不关心内容,只提供读写操作。在复合文档中, LockBytes 对象位于根存储与底层存储介质之间。

Page 39: 第 6 章 可连接对象

ReadAt 和 WriteAt 参数中指定了读写数据在字节序列中的位置。

SetSize 设置字节序列的大小Flush 保证内部缓冲区中的数据被写道实际

的存储介质中Stat 返回当前字节许类的一些状态信息Lockregion加锁UnlockRegion解锁LockBytes 对象可以不实现区域锁功能

Page 40: 第 6 章 可连接对象

LockBytes 对象实现了 ILockBytes 接口。 COM库提供了缺省的基于文件句柄操作的 Lock

Bytes 对象,建立复合文件,也可以利用它建立内存中的复合文档,也可以建立自定义的复合文档。 LockBytes 对象有如磁盘驱动程序,文件可以通过磁盘驱动程序与底层的存储介质传输数据,存储对象通过 LockBytes 对象与底层存储空间交换数据。

在复合文档的层次结构中,根存储通过 LockBytes 对象与底层的存储介质进行数据通信,其它的子对象则通过根存储与底层存储介质进行数据通信,从而实现了整个结构化存储体系结构。

Page 41: 第 6 章 可连接对象

复合文档复合文档 APIAPI 函数介绍函数介绍复合文档作为结构化存储的实现,不仅

建立了 LockBytes 对象的概念,把根存储与底层的存储介质隔离开来,也实现了基于文件句柄和内存句柄的 LockBytes对象,并且, COM还提供了一组 API函数是应用程序可以很方便的使用这些LockBytes 对象以及存储对象和流对象。

Page 42: 第 6 章 可连接对象

(1) 用来创建复合文档分别为: StgCreateDocfile和 StgCreateDocfileOnILockBytes 。

函数 StgCreateDocfile 创建一个复合文档,它使用缺省的基于文件句柄的 LockBytes 对象作为底层的存储机制。 pwcsName 参数指定了文件名。

函数 StgCreateDocfileOnILockBytes 创建一个基于 LockBytes 对象的复合文档,要使用基于内存句柄或自定义作为底层的存储机制,调用此函数。

Page 43: 第 6 章 可连接对象

复合文档复合文档 APIAPI 函数介绍函数介绍 (2) 打开已经存在的复合文档,分别为 StgOpen

Storage 和 StgOpenStorageOnILockBytes 。 这两个函数分别打开基于文件 LockBytes 对象

或其它 LockBytes 对象的复合文档。 StgOpenStorage 函数首先关闭 pstgPriority 存储对象,再跟据 grfMode 参数指定的模式打开新的存储对象,并保存在 ppstgOpen输出参数中,因此函数返回后,就只能使用 ppstgOpen 存储对象而不能再使用 pstgPriority 。参数 snbExclude 用于优化程序性能。

Page 44: 第 6 章 可连接对象

(3) 与内存句柄有关的一组操作函数 CreateILockBytesOnHGlobal 函数可以创建建立在内存句柄基础上的 LockBytes 对象。在调用之前应首先调用 GlobalAlloc 分配一定的内存,如果 LockBytes 对象要跨越进程共享,那么必须使用共享内存, hGlobal 为Null ,那么分配一共享内存。

GetHGlobalFromILockBytes 函数返回内存 LockBytes 对象的内存句柄,可能不一样,原因在于 LockBytes 可能重新分配。

CreateStreamOnHGlobal 函数用来创建一个基于内存的流对象,通常永久对象 (persistent object) 只能作为流对象。

GetHGlobalFromStream 返回流对象句柄。也可能不一样。

Page 45: 第 6 章 可连接对象

复合文档复合文档 APIAPI 函数介绍函数介绍 (4) 还有几个很有用 API 函数,程序中直接可以使用: StgIsStorageFile 函数用来判断一个文件是否是复合文档 StgIsStorageILockBytes 函数用来判断一个 LockBytes 对

象是否包含一个存储对象。 StgSetTimes 函数可以修改一个复合文档的创建、访问、

修改时间值。

Page 46: 第 6 章 可连接对象

零内存保存特性和零内存保存特性和 IRootStorageIRootStorage 接口接口 复合文档提供 LockBytes 对象把根存储对象与底层的文件操作隔离开来,所以在访问存储对象或者流对象时避开了文件句柄操作。当用事务方式打开复合文档时, COM 实际上用到了三个文件句柄:

复合文档句柄;临时文件句柄 ( 保存修该信息 );用于零内存情况下保存文件时预分配的文件句柄。

保存实际上就是把内存中的修改记录写到磁盘文件中,在此 Microsoft 特别考虑了资源的消耗,尤其是内存消耗殆尽的情况下,保证修该信息被正确的保存到文件中。

Page 47: 第 6 章 可连接对象

对于 save 操作,调用 Commit 函数, COM 可以在不利用额外的内存的情况下把修改的信息保存到文件中。

对于 save as 操作,事务模式下的存储对象被分成两部分,一部分是原始的状态信息,另一部分是存放在内存或临时文件中的修改信息,把这两者保存到另一个存储对象中,则必须执行 IStorage::CopyTo 函数,要用额外内存。因此 COM 提供了接口 IRootStorage ,此接口在根存储上实现,它与底层文件系统联系在一起,可以在根存储对象上调用 IStorage::QueryInterface(IID_IRootStorage,…)得到接口指针,接口 IRootStorage 的成员函数 SwitchToFile 利用预留的第三个文件句柄实现了不需要额外内存情况下,把修改信息保存到新文件中。

Page 48: 第 6 章 可连接对象

存储对象、流对象和文件的存储对象、流对象和文件的 CLSIDCLSID 信息信息 在 Istorage 接口中的成员函数 SetClass 用来为

一个存储对象赋一个 CLSID 标识符,以后可以通过 Stat 函数来获取此 CLSID值,实际上,存储对象通过此 CLSID值把它与一段可执行代码联系起来,当客户程序希望执行与存储对象相联系的代码时,它可以利 CLSID值,并调用 CoCreateInstance 函数创建一个 COM 对象,再把存储对象交给 COM 对象,由它处理存储对象。这样的 COM 对象称为永久对象。

COM 也提供 API 函数用于对存储对象或者流对象执行与 CLSID 有关的一些操作。

Page 49: 第 6 章 可连接对象

(1) WriteClassStg 和 ReadClassStg 函数封装了 SetClass 和 Stat 函数,可以完成存储对象的 CLSID 的设置和获取操作。

(2) WriteClassStm 和 ReadClassStm 函数使用一致的格式在流对象的当前位置分别写或读 CLSID 信息,通常情况下,在流的起始位置放 CLSID 信息。

(3) GetClassFile 函数返回一个与给定文件联系的CLSID 。

1. 在 Windows注册表中记录了文件扩展名与 ProgID 之间的联系,而 ProgID 有指定了 CLSID ,所以该文件扩展名与 CLSID联系起来。

2. Windows注册表提供了一些文件匹配规则。

Page 50: 第 6 章 可连接对象

复合文档与结构化存储复合文档与结构化存储 结构化存储中的一些限制: ( 1) 状态标志位没实现, IStorage::SetStateBits 是一个空函数 ( 2) EunmElements 、 MoveElementTo 、 RenameElement 和 Des

toryElement 成员函数没有优化。 ( 3)流对象不支持区域锁操作和事务模式 ( 4)结构化存储允许达到 2 的 64次方,但复合文档只能位 2 的

32次方,因为流的位置指针为 32位值 ( 5) 流对象以 512 个字节为单位申请空间。 ( 6)流对象使用单向链表管理它所占用的非连续的存储空间,向前移动比向后快

( 7)所有的存储对象和流对象的名字都使用 Unicode 字符保存。

Page 51: 第 6 章 可连接对象

永久对象实现永久对象实现 永久对象:因为它有永久状态。它可以把其状态信息

永久保存到存储介质上,以后可以重建这样的对象,并且恢复到原来的状态。

永久接口 客户程序通过永久接口维护永久对象的状态信息,而状

态信息可以存放在各种介质中,比如存储对象、流对象或文件中,根据介质不同, COM 定义了四个常用的永久接口: IPersistStorage 、 IPersistStream 、 IPersistStreamInit 和 IPersistFile 。客户程序可以向这些永久对象请求这些接口,然后通过这些接口读写对象的状态信息。

Page 52: 第 6 章 可连接对象

所有这些永久接口都是从 IPersist 接口派生的。 IPersist 接口中包含了 GetClass 成员函数,客户程序可以通过此函数得到永久对象的 CLSID 。

Load 函数装入状态信息, Save 保存状态信息, IsDirty 是否被修改过。

Page 53: 第 6 章 可连接对象

永久对象实现永久对象实现 GetSizeMax 函数返回状态数据的最大可能值,以便客

户程序预先分配流对象的空间。对实现零内存保存特性有用。 InitNew 函数用于初始化永久对象,这可以给永久对象一个初始化的机会

Save 函数中,参数 lpszFileName 为 NULL ,那么永久对象把状态数据写到文件中,同时清除修改标记并关闭文件。不为 NULL ,择要根据 fRemember 的不同值,或者执行 Save as ,或者执行 Save Copy As 。 SaveCompleted 函数,通知永久对象保存操作已经完成,对象可以再次打开文件。 GetCurFile 函数返回永久对象所使用的文件名字。

Page 54: 第 6 章 可连接对象

IPersistStorage 接口不仅可以实现在事务方式下用存储对象保存永久状态信息,而且也支持零内存保存特性。

HandsOffStorage 函数指示永久对象释放存储对象的所有接口指针,包括所有子对象,以后由客户程序完全控制存储对象。

Page 55: 第 6 章 可连接对象

永久对象的存储特性永久对象的存储特性 为了用流对象保存状态数据,永久对象必须遵循两个规

则: (1) Load 和 Save 函数不能保留流对象的拷贝,也就是说,流对象只在函数内部有效,在函数外部永久对象不能使用流对象。

(2) 在 Load 和 Save 函数内部,永久对象只能访问流对象中从当前位置到 GetSizeMax 函数指定的范围内的数据信息,主要是为了客户程序可以有效的管理流对象。

如果永久对象使用存储对象保存永久状态。主要是 IpersistStorage 接口的三个成员函数 Save 、 SaveCompleted 和HandsOffStorage 如何协调使用。操作过程如下:

Page 56: 第 6 章 可连接对象

永久对象的存储特性永久对象的存储特性 客户程序打开复合文档,得到根存储对象,进一步得

到子存储对象,然后创建永久对象,并调用 InitNew 或Load初始化,永久对象获得对存储对象的控制权,它可以进入编辑状态。保存时,调用 Save 函数之后,永久对象进入一种“待定”状态,此时虽然对象仍控制了存储对象,但不能进行写操作。然后,客户程序调用 SaveCompleted 函数又回到编辑状态,调用 HandsOffStorage 函数,那么永久对象进入“失控”状态,实际上,这是客户强制永久对象释放对象存储对象的所用权,因此,在失控状态下,对象不能进行任何的读写操作,必须等待客户再次调用 SaveCompleted 函数之后,它才能进入编辑状态。

Page 57: 第 6 章 可连接对象

如果客户程序调用 IPersistStorage::Load函数作了初始化操作,那么客户调用 IPersistStorage 之外的永久接口应该被禁止,这主要是为了保持操作的一致性。

Page 58: 第 6 章 可连接对象
Page 59: 第 6 章 可连接对象
Page 60: 第 6 章 可连接对象