结构化存储和OLE对象

 

 

1 引言
目前,传统的二层C/S(Client/Server)结构应用软件已发展为多层结构的分布式应用
系统[2]。为了改善系统的工作效率以及提高系统的伸缩性,很多软件开发人员都把服务器
上的一些基本数据分发到各个客户机上。这种工作模式的优点是显著的,因为它减少了那
些非实时数据(如员工表、产品数据表等)在网络上的流动,并且在网络瘫痪时,各个客
户机仍然可以维持部分的工作。同时,这种模式需要定时在客户机与服务器之间进行数据
更新,为了更有效地进行数据更新,我们设想在客户机上创建类似公文包的数据库,即与
服务器上的SQL Server一样,把所有的数据表保存在几个文件中。围绕这一问题,本文详
细分析了OLE 结构化文件的存储原理,并在Delphi 环境下深入讨论了与其相关的一系列
操作。

2 OLE复合文档的存储原理与操作
2.1 OLE复合文档的存储原理
OLE(Object linking and Embedding),即对象连接与嵌入的简称,是在Windows环境
下实现不同Windows 应用程序之间共享数据的一种方法。OLE 结构化文件,也称为OLE
复合文档,简单来说,OLE复合文档的结构化,实际上是指它的内容按照流(stream)和存
储(storage)的方式进行组织。MicrosoftWord和Excel的文件就是典型的OLE复合文档,
如图1所示,这种文档的内容类似操作系统中的文件系统,即文档内包含“文件夹“和“文
件”。其中,“文件夹”被称为存储,每个存储中所包含的连续数据即“文件”被称为流。
由于OLE复合文档中每一个“文件”都是彼此独立的,所以引言中提出的问题便得到很好
的解决,我们首先建立一个OLE复合文档,然后把众多的数据表以流的方式保存到该复合
文档中,这样,客户机与服务器在进行数据更新时仅是传递几个文件,非常有效。而且,
我们在把Paradox 7格式的数据表写入到OLE复合文档的过程中发现:OLE复合文档的容
量大小约是原来众多数据表容量的总和的50%。
OLE复合文档-----MyOleDoc.ole
DataBase -----存储
Employee -----流
Customer OLE Data
Excel 或AutoCad的数据

2.2 OLE复合文档的建立
可以使用Windows SDK 函数StgCreateDocFile 来建立OLE 复合文档,它的声明在
ActiveX单元中。函数的原形是:
function StgCreateDocfile(pwcsName:PoleStr;grfMode:Longint;reserved:Longint;out stgOpen:IStorage):Hresult;stdcall;
  ,函数返回的存储是复合文档的根目录存储。具体参数如下:
(1) wcsName:被创建的文件名称;
(2) grfMode:复合文档的操作方式,各个选值的含义如表1所示:
(3) reserved:必须设置为0;
(4) stgOpen:返回一个存储;

//grfMode参数意义如下:
STGM_READ 只读模式
STGM_WRITE 只写模式
STGM_READWRITE 读写模式
STGM_SHARE_DENY_NONE 共享存取模式
STGM_SHARE_DENY_READ 禁止共享的读模式
STGM_SHARE_DENY_WRITE 禁止共享的写模式
STGM_SHARE_EXCLUSIVE 独占的存取模式
STGM_DIRECT 对复合文档的所有修改立即生效
STGM_TRANSACTED 提交时所有修改才被保存到复合文档中
STGM_FAILIFTHERE 若已存在一个流或存储,则创建复合文档失败
STGM_CREATE 若已存在一个流或存储,则它将被覆盖,否则将创建一个新的流或存储
STGM_DELETEONRELEASE 当这个复合文档中的流或存储被释放时,它也会自动被释放

2.3 OLE复合文档的打开
可以使用Windows SDK 函数StgOpenStorage 来打开一个OLE 复合文档,它的声明在
ActiveX单元中。函数的原形是:
  function StgOpenStorage(pwcsName: PoleStr;stgPriority:Istorage;
                          grfMode:Longint;snbExclude:TSNB;
                          reserved:Longint;out stgOpen:IStorage):Hresult;stdcall;
 
  ,这里的snbExclude选取nil,其它参数参见StgCreateDocFile()。
 
2.4 流的建立及数据的写入
   打开一个OLE 复合文档后,可用IStorage 接口的CreateStream 函数在该文档中创建一
个流,然后充分利用Delphi强大的流机制与基于OLE的各种应用程序的数据进行信息交换。
例如,用户可以使用Delphi下的OleContainer 控件中加载一个支持OLE 应用程序的数据,
然后调用该控件下的SaveToStream()方法把信息以流的形式写进复合文档。CreateStream 函
数的原形是:
   function CreateStream(pwcsName:PoleStr;grfMode:Longint;
                         reserved1:Longint;reserved2:Longint;
                         out stm:IStream):Hresult;stdcal;
                         
  ,其中,pwcsName是指新建流的名称,reserved1、reserved2两参数的值均置为0,其它参数参见StgCreateDocFile()。
 
2.5 OLE复合文档的存储
如上所述,OLE 复合文档的存储与文件系统的“文件夹”在概念上相似的,它也有着
建立、打开和删除等操作。其中使用IStorage 接口的CreateStorage()、OpenStorage()函数可
以分别建立或打开一个子存储。它们的原形分别是:
function CreateStorage(pwcsName:PoleStr; grfMode: Longint;
dwStgFmt: Longint; reserved2: Longint; out stg: IStorage): Hresult;stdcall;
function OpenStorage(pwcsName: PoleStr; const stgPriority: Istorage;
grfMode:Longint; snbExclude: TSNB; reserved: Longint;
out stg: IStorage): Hresult; stdcall;,它们的参数与上述类同,具体用法
见源代码部分。

2.6 存储和流的删除
使用IStorage接口的DestroyElement()函数可以删除OLE复合文档的存储或流,它的函
数原形是:
   function DestroyElement(pwcsName:POleStr):Hresult;stdcall;
   ,其中,pwcsName参数是指被删除的存储或流的名称。应该指出的是,在删除复合文档的存储或流时,调用
DestroyElement()函数的接口应是被删除的存储或流的上一层存储。如图1 中,若想删除
“Customer”流,则正确的语句是:DataBase·DestroyElement('Customer');而要删
除“DataBase” 存储时,则应使用RootStorage·DestroyElement('DataBase'),其中
RootStorage是根目录存储。此外,当一个流或存储被删除时,它的数据并没有被物理删除,

3 主要源代码
    本程序将创建一个名为MyOleDoc.ole 的复合文档,在该复合文档中,有一个名为
Database的存储,该存储中保存着两个流employee、customer,分别对应着两个数据表,
如图1所示。
3.1 相关控件及属性:
(1) 在Unit1单元的接口引用中添加ActiveX,AxCtrls两个单元;声明一个全局变量
Duqu:Boolean=True;,该变量的作用是记录customer流是否被删除。
(2) 程序中所涉及的相关控件及属性见表2。
控件名称类 属性名称值
  Table1 TTable DataBaseName C:\OLE(数据表存放路径) TableName employee.db
Table2 TTable DataBaseName C:\OLE(数据表存放路径)TableName customer.db
DataSetProvider1 TDataSetProvider DataSet Table1
DataSetProvider2 TDataSetProvider DataSet Table2
ClientDataSet1 TClientDataSet ProviderName DataSetProvider1
ClientDataSet2 TClientDataSet ProviderName DataSetProvider2
ClientDataSet3 TClientDataSet 依靠ClientDataSet3、ClientDataSet4 的LoadFromStream()
ClientDataSet4 TClientDataSet 方法从复合文档读取两个数据表;这两个控件全部选用默认值。
表1:程序中主要控件及属性

3.2主要代码
(1)对BitBtn1的OnClick事件编程;新建复合文档、存储和流;并把两个数据表保存
到该复合文档中。
procedure TForm1.BitBtn1Click(Sender: TObject);
var//声明一些相关的变量
    Hre:HResult;
    RootStorage,SubStorage:IStorage ;
    Istr1,Istr2:IStream;
    OleStream1:TOleStream;
begin
    Hre:=StgCreateDocfile('MyOleDoc.ole',STGM_CREATE or STGM_READWRITE or
    STGM_DIRECT or STGM_SHARE_EXCLUSIVE,0,RootStorage);//建立一个名为
    //MyOleDoc.ole复合文档
    if not SUCCEEDED(Hre) then Application.Terminate; //SUCCEEDED()函数的功能是判
    //断复合文档的建立是否成功
    Hre:=RootStorage.CreateStorage('Database',STGM_CREATE or STGM_READWRITE or
    STGM_DIRECT or STGM_SHARE_EXCLUSIVE,0,0,SubStorage); //建立一个名为
    // Database的存储
    if not SUCCEEDED(Hre) then Application.Terminate;//判断存储的建立是否成功
    Hre:=SubStorage.CreateStream('employee',STGM_CREATE or STGM_READWRITE or
    STGM_DIRECT or STGM_SHARE_EXCLUSIVE,0,0,Istr1); //建立一个名为employee
    //的流
    if not SUCCEEDED(Hre) then Application.Terminate; //判断流的建立是否成功
    ClientDataSet1.Active:=True;
    OleStream1:=TOleStream.Create(Istr1);
    ClientDataSet1.SaveToStream(OleStream1); //把employee数据表的数据写入到
    // MyOleDoc.ole 复合文档的Database 存储下,并以名为employee的流进行保存
    OleStream1.Free;
    Hre:=SubStorage.CreateStream('customer',STGM_CREATE or STGM_READWRITE or
    STGM_DIRECT or STGM_SHARE_EXCLUSIVE,0,0,Istr2);
    if not SUCCEEDED(Hre) then Application.Terminate;
    ClientDataSet2.Active:=True;
    OleStream1:=TOleStream.Create(Istr2);
    ClientDataSet2.SaveToStream(OleStream1);// 把customer 数据表的数据写入到
    //MyOleDoc.ole 复合文档的Database 存储下,并以名为customer的流进行保存
    OleStream1.Free;
    DuQu:=True;
end;

(2)对BitBtn2的OnClick事件编程;打开复合文档、存储和流;并从该复合文档读取两个数据表。
procedure TForm1.BitBtn2Click(Sender: TObject);
var
    Hre:HResult;
    RootStorage,SubStorage:IStorage ;
    Istr1,Istr2:IStream;
    OleStream1:TOleStream;
begin
    ClientDataSet4.Active:=False;
    ClientDataSet4.Active:=False;
    Hre:=StgOpenStorage('MyOleDoc.ole',nil, STGM_READWRITE or STGM_DIRECT or
    STGM_SHARE_EXCLUSIVE,nil,0,RootStorage);//打开一个名为MyOleDoc.ole复合文档
    if not SUCCEEDED(Hre) then Application.Terminate;; //判断复合文档的打开是否成功
    Hre:=RootStorage.OpenStorage('Database',nil,STGM_READWRITE or STGM_DIRECT or
    STGM_SHARE_EXCLUSIVE,nil,0,SubStorage);//打开一个名为Database的存储
    if not SUCCEEDED(Hre) then Application.Terminate;; //判断存储的打开是否成功
    Hre:=SubStorage.OpenStream('employee',nil,STGM_READWRITE or STGM_DIRECT or
    STGM_SHARE_EXCLUSIVE,0,Istr1);//打开一个名为employee的流
    if not SUCCEEDED(Hre) then Application.Terminate;; //判断流的打开是否成功
    OleStream1:=TOleStream.Create(Istr1);
    ClientDataSet3.LoadFromStream(OleStream1); //从MyOleDoc.ole 复合文档的Database
    //存储中,读取employee流,并把数据传送给ClientDataSet3组件;
    OleStream1.Free;
    ClientDataSet3.Active:=True;
    if Duqu then
    begin
        Hre:=SubStorage.OpenStream('customer',nil,STGM_READWRITE or STGM_DIRECT or
        STGM_SHARE_EXCLUSIVE,0,Istr2);
        if not SUCCEEDED(Hre) then Application.Terminate;;
        OleStream1:=TOleStream.Create(Istr2);
        ClientDataSet4.LoadFromStream(OleStream1); //从MyOleDoc.ole 复合文档的Database
        //存储中,读取customer流,并把数据传送给ClientDataSet4组件;
        OleStream1.Free;
        ClientDataSet4.Active:=True;
    end;
end;
(3)对BitBtn4的OnClick事件编程;/删除复合文档中的一个流
procedure TForm1.BitBtn4Click(Sender: TObject);
var
    Hre:HResult;
    RootStorage,TempStorage,SubStorage:IStorage ;
    CLS:TCLSID; //是一个16字节的唯一数字
    Sta:TStatStg; //保存IStorage .Stat()返回的信息
    Istr1:IStream;
begin
    Hre:=StgOpenStorage('MyOleDoc.ole',nil, STGM_READWRITE or STGM_DIRECT or
    STGM_SHARE_EXCLUSIVE,nil,0,RootStorage);
    if not SUCCEEDED(Hre) then Application.Terminate;
    RootStorage.Stat(Sta,0); //IStorage .Stat()返回很多根目录存储信息
    CLS:=Sta.clsid; //获取根目录存储唯一标识符
    Hre:=RootStorage.OpenStorage('Database',nil,STGM_READWRITE or STGM_DIRECT or
    STGM_SHARE_EXCLUSIVE,nil,0,SubStorage);//打开一个名为Database的存储
    SubStorage.DestroyElement('Customer'); //删除MyOleDoc.ole 复合文档Database 存
    //储的Customer流
    Hre:=StgCreateDocfile('MyOleDocTemp.ole',STGM_CREATE or STGM_READWRITE or
    STGM_DIRECT or STGM_SHARE_EXCLUSIVE,0,TempStorage);//建立
    //MyOleDocTemp.ole临时复合文档
    if not SUCCEEDED(Hre) then Application.Terminate;
    RootStorage.CopyTo(0,nil,nil,TempStorage);//把MyOleDoc.ole 复合文档的内容复制到临
    //时复合文档中
    RootStorage:=nil; //把MyOleDoc.ole 复合文档的根目录存储置空,以便重新建立该复
    //合文档
    Hre:=StgCreateDocfile('MyOleDoc.ole',STGM_CREATE or STGM_READWRITE or
    STGM_DIRECT or STGM_SHARE_EXCLUSIVE,0,RootStorage);//建立一个名为
    // MyOleDoc.ole复合文档
    if not SUCCEEDED(Hre) then Application.Terminate;
    RootStorage.SetClass(CLS); //设置新文档的CLSID为原来文档的CLSID
    TempStorage.CopyTo(0,nil,nil,RootStorage); // 把临时复合文档的内容复制到新的复合文
    //档中
    TempStorage:=nil;//把临时复合文档的根目录存储置空,以便可以删除它
    deletefile('MyOleDocTemp.ole');
    DuQu:=False;
end;



/////////////////////////////////////////////////////////////////////////////
//范例2,结构化存储读写复合文档的范例
/////////////////////////////////////////////////////////////////////////////
Uses Activex;

type
  TRec = record
    Name: string[8];
    Age: Word;
  end;

const FileName = 'C:\Temp\Test.dat';

procedure TForm1.FormCreate(Sender: TObject);
begin
  Button1.Caption := '写复合文件';
  Button2.Caption := '读复合文件';
  Position := poDesktopCenter;
end;

procedure TForm1.Button1Click(Sender: TObject);
const
  Mode = STGM_CREATE or STGM_READWRITE or STGM_SHARE_EXCLUSIVE;
var
  StgRoot, StgSub: IStorage;
  Stm: IStream;
  Rec1: TRec;
begin
  {建立根 IStorage: StgRoot}
  StgCreateDocfile(FileName, Mode, 0, StgRoot);

  {建立子 IStorage: StgSub}
  StgRoot.CreateStorage('StgSub', Mode, 0, 0, StgSub);

  {在子 IStorage: StgSub 中建立 IStream: Stm}
  StgSub.CreateStream('Stm', Mode, 0, 0, Stm);

  {写入数据}
  Rec1.Name := '张三';
  Rec1.Age := 99;
  Stm.Write(@Rec1, SizeOf(TRec), nil);
end;

procedure TForm1.Button2Click(Sender: TObject);
const
  Mode = STGM_READ or STGM_SHARE_EXCLUSIVE;
Var
  StgRoot, StgSub :IStorage;
  Stm: IStream;
  Rec1: TRec;
Begin
  {如果不是结构化存储文件则退出}
  if StgIsStorageFile(FileName) <> S_OK then Exit;

  {获取根 IStorage: StgRoot}
  StgOpenStorage(FileName, nil, Mode, nil, 0, StgRoot);

  {获取子 IStorage: StgSub; 注意: 第一个参数的名称必须和保存时一致}
  StgRoot.OpenStorage('StgSub', nil, Mode, nil, 0, StgSub);

  {获取 IStream: Stm; 注意: 第一个参数的名称必须和保存时一致}
  StgSub.OpenStream('Stm', nil, Mode, 0, Stm);

  {读出数据}
  Stm.Read(@Rec1, SizeOf(TRec), nil);
  ShowMessageFmt('%s, %d', [Rec1.Name, Rec1.Age]);
end;

end.

posted @ 2009-01-19 11:56  巩固  阅读(1825)  评论(0编辑  收藏  举报