结构化存储.docx

上传人:b****5 文档编号:30715904 上传时间:2023-08-19 格式:DOCX 页数:23 大小:61.38KB
下载 相关 举报
结构化存储.docx_第1页
第1页 / 共23页
结构化存储.docx_第2页
第2页 / 共23页
结构化存储.docx_第3页
第3页 / 共23页
结构化存储.docx_第4页
第4页 / 共23页
结构化存储.docx_第5页
第5页 / 共23页
点击查看更多>>
下载资源
资源描述

结构化存储.docx

《结构化存储.docx》由会员分享,可在线阅读,更多相关《结构化存储.docx(23页珍藏版)》请在冰豆网上搜索。

结构化存储.docx

结构化存储

1、结构化存储

      COM的结构化存储(structuredstorage)机制,也称为永久存储(persistentstorage)机制。

结构化存储可以说是软件存储技术的一个重要进展,COM针对组件软件的需要,在文件系统的基础上,提出了结构化存储的概念。

利用结构化存储,组件程序之间可很好地协同工作,一个组件程序可以与另一个组件程序共享同一个文件,就如同一个应用程序与另一个应用程序共享同一个磁盘文件系统一样。

      COM定义了结构化存储的规范,包括一组接口和实现这些接口成员函数的一些规则;同时COM也提供了结构化存储的实现,即复合文档技术。

复合文档技术是OLE的基础。

OLE最初的目标是在文档中嵌入或链接对象,当然现在OLE的发展已经超出了这个范围,但复合文档仍然是OLE的基础存储技术。

 

2、结构化存储的引入

      组件化程序设计方法把应用系统设计成多个组件程序,如何在这些组件程序之间以共享方式访问同一个文件是组件化程序设计必须要解决的问题。

而多个组件通过文件句柄访问共享文件难以实现,结构化存储技术“借用”文件系统的概念,在文件内部构造了一个类似于文件系统的树状层次结构,解决了这一问题。

      结构化存储的层次结构的节点可以是两种对象:

存储对象和流对象,每个存储对象或者流对象都是一个可独立进行读写操作的对象,组件程序只对它拥有的节点对象进行操作。

从应用系统整体上看,这些组件程序在共享访问同一个文件。

 

3、文件系统

      操作系统的诞生把应用程序与底层存储设备隔离开来,操作系统为应用程序的运行提供了基本的抽象环境,它可以处理所有与存储设备有关的基本操作。

同时,操作系统引入了文件系统的概念,允许多个应用程序共享同一个存储设备。

而且,操作系统为应用程序提供了一种抽象的流式存储结构,应用程序仍然独立地访问它自己的存储空间,不同应用程序相互之间不受干扰。

 

4、文件系统与结构化存储的框图

5、存储对象和流对象

      对于一个完整的存储操作来说,它被分为两个层次:

应用程序调用API函数;操作系统提供API函数的实现。

COM库提供了结构化存储的实现,它提供了一组接口和API函数供组件程序调用来完成实际的存储操作。

因此,结构化存储定义的存储对象和流对象由COM库实现,应用程序或者组件程序并不需要实现这两个对象,就好比应用程序不需要实现文件句柄或者目录对象一样。

      流对象非常类似于单独的磁盘文件,它也是进行数据读写操作的基本对象,利用流对象可以保存各种类型的数据,它有自身的访问权限和一个独立的搜索指针。

流对象也用一个字符串作为其名称,就好像文件名一样。

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

      存储对象类似于目录对象,它也有一个字符串名称,但它本身并没有存储数据信息,它作为其子存储对象和子流对象的容器,只记录了这些子对象的信息。

存储对象暴露IStorage接口,客户程序通过IStorage接口对存储对象进行操作。

      应用程序或者组件程序可以通过结构化存储机制共享同一个复合文件,存储对象和流对象也可以在这些程序之间被共享访问,即使这些程序运行在不同的进程中。

 

6、结构化存储特性――访问模式

      存储对象和流对象支持两种基本的访问模式:

直接访问和事务访问模式。

在直接访问模式下,程序对存储对象或者流对象的修改操作马上生效;而在事务模式下,程序对存储对象或者流对象所作的修改被缓存起来,只有当提交(Commit)时才真正有效,如果调用Revert成员函数,则可以恢复到上次提交或刚打开时的状态。

 

7、结构化存储特性――事务机制

      在结构化存储的树状层次结构中,事务特性可以适用于所有层次上的对象,因此,事务特性可以嵌套使用。

      事务机制需要消耗较多系统资源。

 

8、结构化存储特性――命名规则

      在结构化存储的树状层次结构中,每个存储对象和流对象都有一个字符串名字。

      根存储对象的名字实际上就是复合文档的文件名,所以根存储对象的命名规则受文件系统影响,它遵守文件系统的命名约定。

与创建和打开根存储对象有关的函数中,直接使用文件名即可。

所有非根存储对象和流对象都由它们的父对象管理,由于它们存在于文件内部,因此它们的命名规则遵守COM给出的约定。

 

9、结构化存储特性――增量访问

      增量访问的意义在于减少了保存和打开文件时的操作时间和降低了应用程序对系统资源的要求。

      对于大的复合文件的编辑操作,内存往往是个瓶颈,结构化存储本身也需要消耗一些系统内存资源,但它实现的增量访问反而降低了应用程序对内存的要求,同时也提高了系统的性能。

      结构化存储也带来了另一个问题,那就是空间回收的管理。

频繁地对同一个复合文件进行修改、保存,则文件的尺寸总是在增长,原因在于删除对象时,COM只是把这些所占用的磁盘空间标记为“未用”,而没有释放这些磁盘空间。

当然COM以后可能会重用这些空间,但在重用之前,这些空间仍保留在文件中。

解决这个问题的方法是:

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

      MicrosoftAccess或Word产生的文件就会出现这种情况。

 

10、结构化存储实现:

复合文档

      从结构化存储的对象结构可以看出,要在特定的系统平台上实现结构化存储,关键在两方面:

一是如何把根存储与底层存储介质结合起来,二是实现存储对象和流对象。

复合文档通过一个被称为“LockBytes”的对象,把根存储与底层的存储介质联系起来,其他的子对象则通过根存储与底层存储介质进行数据通信,从而实现了整个结构化存储体系结构。

底层介质不仅可以是磁盘文件,复合文档也允许是内存空间,甚至是用户自定义的虚拟空间。

LockBytes对象实际上是所有存储介质的一种抽象表达方式,它把存储介质描述成一般化的字节序列,不管是磁盘文件还是内存区域都可以按字节序列对待。

COM库提供了缺省的基于文件句柄操作的LockBytes对象,我们可以利用此LockBytes对象建立复合文件。

COM库还提供了基于内存的LockBytes对象,我们可以利用内存LockBytes对象建立内存中的复合文档。

而且,COM还允许我们实现自己的LockBytes对象,并在自定义LockBytes对象的基础上建立复合文档。

 

11、复合文档API函数

      创建复合文档API函数:

StgCreateDocfile和StgCreateDocfileOnLockBytes。

      打开已经存在的复合文档API函数:

StgOpenStorage和StgOpenStorageOnLockBytes。

      在内存基础上创建LockBytes对象或者流对象的API函数:

CreateILockBytesOnHGlobal、GetHGlobalFromILockBytes、CreateStreamOnHGlobal、GetHGlobalFromStream。

      其他的API函数:

StgIsStorageFile和StgSetTimes。

 

12、零内存保存特性和IRootStorage接口

      复合文档通过LockBytes对象把根存储对象与底层的文件操作隔离开来,所以我们在访问存储对象或者流对象时避开了文件句柄操作。

当我们用事务方式打开复合文件时,COM实际上用到了三个文件句柄,一个是复合文件句柄,另一个是临时文件句柄,该临时文件记录了存储对象操作过程中的修改信息,还有一个句柄用作在零内存情况下保存文件时预分配的文件句柄。

 

13、存储对象、流对象和文件的CLSID信息

      通过IStorage接口的SetClass函数可以为一个存储对象赋一个CLSID标识符,并可通过Stat函数获取此CLSID值。

实际上,存储对象通过此CLSID值把它与一段可执行代码联系起来,当客户程序希望执行与存储对象相联系的代码时,它利用CLSID值,并调用CoCreateInstamce函数创建一个COM对象,再把存储对象交给COM对象,由它处理存储对象。

这样的COM对象称为永久对象(persistentobject),它通常实现了IPersist***接口,比如IPersistFile、IPersistStorage、IPersistStream和IPersistStreamInit等,客户程序通过这些接口进行数据交换。

      COM也提供了几个API函数用于存储对象或者流对象执行与CLSID有关的一些常规操作:

(1)WriteClassStg和ReadClassStg函数封装了IStorage:

:

SetClass和IStorage:

:

Stat成员函数,可以完成存储对象的CLSID的设置和获取操作。

存储对象只是个容器,它本身不包含数据信息,所以它的CLSID信息被写在其下面的一个子流对象中,其名字为“\x01CompObj”。

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

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

Windows桌面环境利用它把数据文件与应用程序联系起来,实现了桌面环境中的数据驱动机制。

对于非复合文档,Windows提供了两种方法可以建立这样的联系。

(1)Windows系统注册记录了文件扩展名与ProgID之间的联系,而ProgID又指定了CLSID,所以该文件扩展名与CLSID联系起来了。

(2)Windows系统注册表提供了一些文件匹配规则,在HKEY_CLASSES_ROOT\FilType键下记录了一些CLSID与它们的匹配规则。

 

14、复合文档与结构化存储

      结构化存储是COM规范的一部分,它描述了一个理想的存储机制,而复合文档作为它在Windows平台上的实现,它利用操作系统的特点增加了一些新的特性,但也不可避免地受到一些限制。

复合文档是目前Windows平台上的基本存储机制。

 

15、永久接口

      客户程序通过永久接口维护永久对象的状态信息,而状态信息可以被存放在各种介质中,比如存储对象、流对象或者文件中,根据介质的不同,COM定义了四个常用的永久接口:

IPersistFile、IPersistStorage、IPersistStream和IPersistStreamInit,它们都派生自基本的IPersist接口。

客户程序可以向永久对象请求这些接口,然后通过接口读写对象的状态信息。

 

16、永久对象的存储特性

      永久对象通过三种介质保存状态数据,分别为流对象、存储对象和文件。

永久对象在三种介质上保存状态数据的特性是不同的,操作过程也是不同的。

      实际上,一个永久对象可以实现多个永久接口以便提供多种介质的存储支持。

从程序实现来讲,这是一个多接口支持问题,从客户程序来讲,它只能使用一种接口,根据客户程序的特性,它可以优先选择一个永久接口。

 

COM编程入门第一部分——什么是COM,如何使用COM

本文的目的是为刚刚接触COM的程序员提供编程指南,并帮助他们理解COM的基本概念。

内容包括COM规范简介,重要的COM术语以及如何重用现有的COM组件。

本文不包括如何编写自己的COM对象和接口。

  COM即组件对象模型,是ComponentObjectModel取前三个字母的缩写,这三个字母在当今Windows的世界中随处可见。

随时涌现出来的大把大把的新技术都以COM为基础。

各种文档中也充斥着诸如COM对象、接口、服务器之类的术语。

因此,对于一个程序员来说,不仅要掌握使用COM的方法,而且还要彻底熟悉COM的所有一切。

  本文由浅入深描述COM的内在运行机制,教你如何使用第三方提供的COM对象(以Windows外壳组件Shell为例)。

读完本文后,你就能掌握如何使用Windows操作系统中内建的组件和第三方提供的COM对象。

  本文假设你精通C++语言。

在例子代码中使用了一点MFC和ATL,如果你不熟悉MFC和ATL也没关系,本文会对这些代码进行完全透彻的解释。

本文包括以下几个部分:

∙COM——到底是什么?

——COM标准的要点介绍,它被设计用来解决什么问题

∙基本元素的定义——COM术语以及这些术语的含义

∙使用和处理COM对象——如何创建、使用和销毁COM对象

∙基本接口——描述IUnknown基本接口及其方法

∙掌握串的处理——在COM代码中如何处理串

∙应用COM技术——例子代码,举例说明本文所讨论的所有概念

∙处理HRESULT——HRESULT类型描述,如何监测错误及成功代码

COM——到底是什么

  简单地说,COM是一种跨应用和语言共享二进制代码的方法。

与C++不同,它提倡源代码重用。

ATL便是一个很好的例证。

源码级重用虽然好,但只能用于C++。

它还带来了名字冲突的可能性,更不用说不断拷贝重用代码而导致工程膨胀和臃肿。

  Windows使用DLLs在二进制级共享代码。

这也是Windows程序运行的关键——重用kernel32.dll,user32.dll等。

但DLLs是针对C接口而写的,它们只能被C或理解C调用规范的语言使用。

由编程语言来负责实现共享代码,而不是由DLLs本身。

这样的话DLLs的使用受到限制。

MFC引入了另外一种MFC扩展DLLs二进制共享机制。

但它的使用仍受限制——只能在MFC程序中使用。

  COM通过定义二进制标准解决了这些问题,即COM明确指出二进制模块(DLLs和EXEs)必须被编译成与指定的结构匹配。

这个标准也确切规定了在内存中如何组织COM对象。

COM定义的二进制标准还必须独立于任何编程语言(如C++中的命名修饰)。

一旦满足了这些条件,就可以轻松地从任何编程语言中存取这些模块。

由编译器负责所产生的二进制代码与标准兼容。

这样使后来的人就能更容易地使用这些二进制代码。

  在内存中,COM对象的这种标准形式在C++虚函数中偶尔用到,所以这就是为什么许多COM代码使用C++的原因。

但是记住,编写模块所用的语言是无关的,因为结果二进制代码为所有语言可用。

  此外,COM不是Win32特有的。

从理论上讲,它可以被移植到Unix或其它操作系统。

但是我好像还从来没有在Windows以外的地方听说过COM。

基本元素的定义

  我们从下往上看。

接口只不过是一组函数。

这些函数被称为方法。

接口名字以大写的I开头,例如C++中的IShellLink,接口被设计成一个抽象基类,其中只有纯粹的虚拟函数。

  接口可以从其它接口继承,这里所说的继承的原理就好像C++中的单继承。

接口是不允许多继承的。

coclass(简称组件对象类——componentobjectclass)被包含在DLL或EXE中,并且包含着一个或者多个接口的代码。

组件对象类(coclasss)实现这些接口。

COM对象在内存中表现为组件对象类(coclasss)的一个实例。

注意COM“类”和C++“类”是不相同的,尽管常常COM类实现的就是一个C++类。

 COM服务器是包含了一个或多个coclass的二进制(DLL或EXE)。

注册(Registration)是创建注册表入口的一个过程,告诉Windows操作系统COM服务器放在什么位置。

取消注册(Unregistration)则相反——从注册表删除这些注册入口。

GUID(谐音为“fluid”,意思是全球唯一标示符——globallyuniqueidentifier)是个128位的数字。

它是一种独立于COM编程语言的标示方法。

每一个接口和coclass有一个GUID。

因为每一个GUID都是全球唯一的,所以避免了名字冲突(只要你用COMAPI创建它们)。

有时你还会碰到另一个术语UUID(意思也是全球唯一标示符——universallyuniqueidentifier)。

UUIDs和GUIDs在实际使用时的用途是一样的。

类ID或者CLSID是命名coclass的GUID。

接口ID或者IID是命名接口的GUID。

在COM中广泛地使用GUID有两个理由:

1.GUIDs只是简单的数字,任何编程语言都可以对之进行处理;

2.GUIDs可以在任何机器上被任何人创建,一旦完成创建,它就是唯一的。

因此,COM开发人员可以创建自己特有的GUIDs而不会与其它开发人员所创建的GUIDs有冲突。

这样就消除了集中授权发布GUIDs的必要。

  HRESULT是COM用来返回错误和成功代码的整型数字。

除此之外,别无它意,虽然以H作前缀,但没有句柄之意。

下文会对它有更多的讨论。

  最后,COM库是在你使用COM时与你交互的操作系统的一部分,它常常指的就是COM本身。

但是为了避免混淆才分开描述的。

使用和处理COM对象

  每一种语言都有其自己处理对象的方式。

例如,C++是在栈中创建对象,或者用new动态分配。

因为COM必须独立于语言,所以COM库为自己提供对象管理例程。

下面是对COM对象管理和C++对象管理所做的一个比较:

创建一个新对象

C++中,用new操作符,或者在栈中创建对象。

COM中,调用COM库中的API。

删除对象

C++中,用delete操作符,或将栈对象踢出。

COM中,所有的对象保持它们自己的引用计数。

调用者必须通知对象什么时候用完这个对象。

当引用计数为零时,COM对象将自己从内存中释放。

  由此可见,对象处理的两个阶段:

创建和销毁,缺一不可。

当创建COM对象时要通知COM库使用哪一个接口。

如果这个对象创建成功,COM库返回所请求接口的指针。

然后通过这个指针调用方法,就像使用常规C++对象指针一样。

创建COM对象

为了创建COM对象并从这个对象获得接口,必须调用COM库的API函数,CoCreateInstance()。

其原型如下:

HRESULTCoCreateInstance(

REFCLSIDrclsid,

LPUNKNOWNpUnkOuter,

DWORDdwClsContext,

REFIIDriid,

LPVOID*ppv);

以下是参数解释:

rclsid:

coclass的CLSID,例如,可以传递CLSID_ShellLink创建一个COM对象来建立快捷方式。

pUnkOuter:

这个参数只用于COM对象的聚合,利用它向现有的coclass添加新方法。

参数值为null表示不使用聚合。

dwClsContext:

表示所使用COM服务器的种类。

本文使用的是最简单的COM服务器,一个进程内(in-process)DLL,

        所以传递的参数值为CLSCTX_INPROC_SERVER。

注意这里不要随意使用CLSCTX_ALL(在ATL中,它是个缺省值),

        因为在没有安装DCOM的Windows95系统上会导致失败。

riid:

请求接口的IID。

例如,可以传递IID_IShellLink获得IShellLink接口指针。

ppv:

接口指针的地址。

COM库通过这个参数返回请求的接口。

  当你调用CoCreateInstance()时,它负责在注册表中查找COM服务器的位置,将服务器加载到内存,并创建你所请求的coclass实例。

以下是一个调用的例子,创建一个CLSID_ShellLink对象的实例并请求指向这个对象IShellLink接口指针。

HRESULThr;

IShellLink*pISL;

hr=CoCreateInstance(CLSID_ShellLink,//coclass的CLSID

NULL,//不是用聚合

CLSCTX_INPROC_SERVER,//服务器类型

IID_IShellLink,//接口的IID

(void**)&pISL);//指向接口的指针

if(SUCCEEDED(hr))

{

//用pISL调用方法

}

else

{

//不能创建COM对象,hr为出错代码

}

  首先声明一个接受CoCreateInstance()返回值的HRESULT和IShellLink指针。

调用CoCreateInstance()来创建新的COM对象。

如果hr接受到一个表示成功的代码,则SUCCEEDED宏返回TRUE,否则返回FALSE。

FAILED是一个与SUCCEEDED对应的宏用来检查失败代码。

删除COM对象

  前面说过,你不用释放COM对象,只要告诉它们你已经用完对象。

IUnknown是每一个COM对象必须实现的接口,它有一个方法,Release()。

调用这个方法通知COM对象你不再需要对象。

一旦调用了这个方法之后,就不能再次使用这个接口,因为这个COM对象可能从此就从内存中消失了。

  如果你的应用程序使用许多不同的COM对象,因此在用完某个接口后调用Release()就显得非常重要。

如果你不释放接口,这个COM对象(包含代码的DLLs)将保留在内存中,这会增加不必要的开销。

如果你的应用程序要长时间运行,就应该在应用程序处于空闲期间调用CoFreeUnusedLibraries()API。

这个API将卸载任何没有明显引用的COM服务器,因此这也降低了应用程序使用的内存开销。

继续用上面的例子来说明如何使用Release():

//像上面一样创建COM对象,然后,

if(SUCCEEDED(hr))

{

//用pISL调用方法

//通知COM对象不再使用它

pISL->Release();

}

接下来将详细讨论IUnknown接口。

基本接口——IUnknown

  每一个COM接口都派生于IUnknown。

这个名字有点误导人,其中没有未知(Unknown)接口的意思。

它的原意是如果有一个指向某COM对象的IUnknown指针,就不用知道潜在的对象是什么,因为每个COM对象都实现IUnknown。

IUnknown有三个方法:

∙AddRef()——通知COM对象增加它的引用计数。

如果你进行了一次接口指针的拷贝,就必须调用一次这个方法,并且原始的值和拷贝的值两者都要用到。

在本文的例子中没有用到AddRef()方法;

∙Release()——通知COM对象减少它的引用计数。

参见前面的Release()示例代码段;

∙QueryInterface()——从COM对象请求一个接口指针。

当coclass实现一个以上的接口时,就要用到这个方法;

  前面已经看到了Release()的使用,但如何使用QueryInterface()呢?

当你用CoCreateInstance()创建对象的时候,你得到一个返回的接口指针。

如果这个COM对象实现一个以上的接口(不包括IUnknown),你就必须用QueryInterface()方法来获得任何你需要的附加的接口指针。

QueryInterface()的原型如下:

HRESULTIUnknown:

:

QueryInterface(

REFIIDiid,

void**ppv);

以下是参数解释:

ii

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 求职职场 > 简历

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1