DELPHI基础教程:开发Delphi对象式数据管理功能(一)[4]

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DELPHI基础教程:开发Delphi对象式数据管理功能(一)[4]相关的知识,希望对你有一定的参考价值。

参考技术A

  function THandleStream Write(const Buffer; Count: Longint) Longint;

  begin

  Result := FileWrite(FHandle Buffer Count)

  if Result = then Result := ;

  end;

  Seek方法调用FileSeek函数实现文件指针的移动 其实现如下

  function THandleStream Seek(Offset: Longint; Origin: Word) Longint;

  begin

  Result := FileSeek(FHandle Offset Origin)

  end;

   TFileStream对象

  TFileStream对象是在磁盘文件上存储数据的Stream对象 TFileStream是从THandleStream继承下来的 它和THandleStream一样都是实现文件的存取操作 不同之处在于THandleStream用句柄访问文件 而TFileStream用文件名访问文件 实际上TFileStream是THandleStream上的一层包装 其内核是THandleStream的属性和方法

  TFileStream中没有增加新的属性和方法 它只是覆盖了的构造方法Create和析构方法Destory 在Create方法中带两个参数FileName和Mode FileName描述要创建或打开的文件名 而Mode描述文件模式如fmCreate fmOpenRead和fmOpenWrite等 Create方法首先使用FileCreate或FileOpen函数创建或打开名为FileName的文件 再将得到的文件句柄赋给FHandle TFileStream的文件读写操作都是由从THandleStream继承的Read

  var

  Stream: TStream;

  begin

  Stream := TFileStream Create(FileName fmCreate)

  try

  SaveToStream(Stream)

  finally

  Stream Free;

  end;

  end;

  在Delphi 的许多对象的SaveToStream 和SaveToFile LoadFromStream和LoadFromFile方法的实现都有类似的嵌套结构

   TMemoryStream对象

  TMemoryStream对象是一个管理动态内存中的数据的Stream对象 它是从TCustomMemoryStream中继承下来的 除了从TCustomMemoryStream中继承的属性和方法外 它还增加和覆盖了一些用于从磁盘文件和其它注台读数据的方法 它还提供了写入 消除内存内容的动态内存管理方法 下面介绍它的这些属性和方法

   TMemoryStream的属性和方法

   Capacity属性

  声明

property Copacity: Longint;

  Capacity属性决定了分配给内存流的内存池的大小 这与Size属性有些不同 Size属性是描述流中数据的大小 在程序中可以将Capacity 的值设置的比数据所需最大内存大一些 这样可以避免频繁地重新分配

   Realloc方法

  声明

function Realloc(var NewCapacity: Longint) Pointer; virtual;

  Realloc方法 以 K为单位分配动态内存 内存的大小由NewCapacity指定 函数返回指向所分配内存的指针

   SetSize方法

  SetSize方法消除内存流中包含的数据 并将内存流中内存池的大小设为Size字节 如果Size为零 是SetSize方法将释放已有的内存池 并将Memory属性置为nil;否则 SetSize方法将内存池大小调整为Size

   Clear方法

  声明

procedure Clear;

  Clear方法释放内存中的内存池 并将Memory属性置为nil 在调用Clear方法后 Size和Position属性都为

   LoadFromStream方法

  声明

procedure LoadFromStream(Stream: TStream)

  LoadFromStream方法将Stream指定的流中的全部内容复制到MemoryStream中 复制过程将取代已有内容 使MemoryStream成为Stream的一份拷贝

   LoadFromFile方法

  声明

procedure LoadFromFile(count FileName: String)

  LoadFromFile方法将FileName指定文件的所有内容复制到MemoryStream中 并取代已有内容 调用LoadFromFile方法后 MemoryStream将成为文件内容在内存中的完整拷贝

   TMemoryStream对象的实现原理

  TMemoryStream从TCustomMemoryStream对象直接继承 因此可以享用TCustomMemoryStream的属性和方法 前面讲过 TCustomMemoryStream是用于内存中数据操作的抽象对象 它为MemoryStream对象的实现提供了框架 框架中的内容还要由具体MemoryStream对象去填充 TMemoryStream对象就是按动态内存管理的需要填充框架中的具体内容 下面介绍TMemoryStream对象的实现

    TMemoryStream属性的实现

  TMemoryStream在其protected部分增加了一个Capacity属性 该属性决定了MemoryStream所占动态内存的大小 TMemoryStream首先在private部分声明了FCapacity变量作为存储Capacity属性值的数据域 然后在protected部分声明了该属性 在属性声明的读控制部分简单读取FCapacity的值 在写控制处调用了方法SetCapacity 该方法除了给FCapacity赋值外还执行了修改Capacity属性所必需操作如状态改变等

  下面是属性的实现

  TMemoryStream = class(TCustomMemoryStream)

  private

  FCapacity: Longint;

  procedure SetCapacity(NewCapacity: Longint)

  protected

  …

  property Capacity: Longint read FCapacity write SetCapacity;

  public

  …

  end;

  写控制方法SetCapacity的实现是这样的

  procedure TMemoryStream SetCapacity(NewCapacity: Longint)

  begin

  SetPointer(Realloc(NewCapacity) FSize)

  FCapacity := NewCapacity;

  end;

  在SetCapacity 方法先是调用Realloc重新分配内存 然后用NewCapacity的值给FCapacity赋值 Realloc方法进行某些对象状态的改变

   TMemoryStream对象方法的实现

  ⑴ Realloc方法

  Realloc方法是TMemoryStream动态内存分配的核心 它的SetSize SetCapacity等方法最终都是调用Realloc进行内存的分配和初始化工作的 它的实现如下

  const

  MemoryDelta = $ ;

  function TMemoryStream Realloc(var NewCapacity: Longint) Pointer;

  begin

  if NewCapacity > then

  NewCapacity := (NewCapacity + (MemoryDelta )) and not (MemoryDelta )

  Result := Memory;

  if NewCapacity <> FCapacity then

  begin

  if NewCapacity = then

  begin

  GlobalFreePtr(Memory)

  Result := nil;

  end else

  begin

  if Capacity = then

  Result := GlobalAllocPtr(HeapAllocFlags NewCapacity)

  else

  Result := GlobalReallocPtr(Memory NewCapacity HeapAllocFlags)

  if Result = nil then raise EStreamError CreateRes(SMemoryStreamError)

  end;

  end;

  end;

  Realloc方法是以 K为单位分配动态内存的 方法中的第一句if语句就是执行该操作 如果传入的NewCapacity参数值为 则释放流中的内存 Realloc方法用GLobal FreePtr函数释放内存 用GlobalAllocPtr分配内存 用GlobalReallocPtr进行内存的重分配 如果原来的Capacity属性值为 则调用Globa|AllocPtr否则调用GlobalReallocPtr 最后如果Result为nil则触发内存流错的异常事件 否则返回指向分配的内存的指针

lishixinzhi/Article/program/Delphi/201311/25107

Delphi的组件读写机制

Delphi的组件读写机制(一) 
一、流式对象(Stream)和读写对象(Filer)的介绍
在面向对象程序设计中,对象式数据管理占有很重要的地位。在Delphi中,对对象式数据管理的支持方式是其一大特色。 
Delphi是一个面向对象的可视化设计与面向对象的语言相结合的集成开发环境。Delphi的核心是组件。组件是对象的一种。Delphi应用程序完全是由组件来构造的,因此开发高性能的Delphi应用程序必然会涉及对象式数据管理技术。 
对象式数据管理包括两方面的内容:
● 用对象来管理数据
● 对各类数据对象(包括对象和组件)的管理 
Delphi将对象式数据管理类归结为Stream对象(Stream)和Filer对象(Filer),并将它们应用于可视组件类库(VCL)的方方面面。它们提供了丰富的在内存、外存和Windows资源中管理对象的功能,Stream对象,又称流式对象,是TStream、THandleStream、TFileStream、TMemoryStream、TResourceStream和TBlobStream等的统称。它们分别代表了在各种媒介上存储数据的能力,它们将各种数据类型(包括对象和组件) 在内存、外存和数据库字段中的管理操作抽象为对象方法,并且充分利用了面向对象技术的优点,应用程序可以相当容易地在各种Stream对象中拷贝数据。
读写对象(Filer)包括TFiler对象、TReader对象和TWriter对象。TFiler对象是文件读写的基础对象,在应用程序中使用的主要是TReader和TWriter。TReader和TWriter对象都直接从TFiler对象继承。TFiler对象定义了Filer对象的基本属性和方法。
  Filer对象主要完成两大功能:
● 存取窗体文件和窗体文件中的组件
● 提供数据缓冲,加快数据读写操作 
为了对流式对象和读写对象有一个感性的认识,先来看一个例子。
a)写文件
procedure TFomr1.WriteData (Sender: TObject);
Var
FileStream:TFilestream;
Mywriter:TWriter;
i: integer
Begin
FileStream:=TFilestream.create(‘c:\Test.txt’,fmopenwrite);//创建文件流对象
Mywriter:=TWriter.create(FileStream,1024); //把Mywriter和FileStream联系起来
Mywriter. writelistbegin;  //写入列表开始标志
For i:=0 to Memo1.lines.count-1 do   
Mywriter.writestring(memo1.lines[i]); //保存Memo组件中文本信息到文件中
Mywriter.writelistend;          //写入列表结束标志
FileStream.seek(0,sofrombeginning); //文件流对象指针移到流起始位置
Mywriter.free; //释放Mywriter对象
FileStream.free; //释放FileStream对象
End;
b)读文件
procedure TForm1.ReadData(Sender: TObject);
Var
  FileStream:TFilestream;
  Myreader:TReader;
Begin
  FileStream:=TFilestream.create(‘c:\Test.txt’,fmopenread); 
  Myreader:=TRreader.create(FileStream,1024); //把Myreader和FileStream联系起来
  Myreader.readlistbegin;  //把写入的列表开始标志读出来
  Memo1.lines.clear; //清除Memo1组件的文本内容
  While not myreader.endoflist do //注意TReader的一个方法:endoflist
  Begin
    Memo1.lines.add(myreader.readstring); //把读出的字符串加到Memo1组件中
  End;
  Myreader.readlistend; //把写入的列表结束标志读出来
  Myreader.free;  //释放Myreader对象
  FileStream.free; //释放FileStream对象
End;
上面两个过程,一个为写过程,另一个为读过程。写过程通过TWriter,利用TFilestream把一个Memo中的内容(文本信息)存为一个保存在磁盘上的二进制文件。读过程刚好和写过程相反,通过TReader,利用TFilestream把二进制文件中的内容转换为文本信息并显示在Memo中。运行程序可以看到,读过程忠实的把写过程所保存的信息进行了还原。
值得注意的是,读写对象如TFiler对象、TReader对象和TWriter对象等很少由应用程序编写者进行直接的调用,它通常用来读写组件的状态,它在读写组件机制中扮演着非常重要的角色。
对于流式对象Stream,很多参考资料上都有很详细的介绍,而TFiler对象、TReader对象和TWriter对象特别是组件读写机制的参考资料则很少见,本文将通过对VCL原代码的跟踪而对组件读写机制进行剖析。 
二、读写对象(Filer)与组件读写机制
Filer对象主要用于存取Delphi的窗体文件和窗体文件中的组件,所以要清楚地理解Filer对象就要清楚Delphi 窗体文件(DFM文件)的结构。
  DFM文件是用于Delphi存储窗体的。窗体是Delphi可视化程序设计的核心。窗体对应Delphi应用程序中的窗口,窗体中的可视组件对应窗口中的界面元素,非可视组件如TTimer和TOpenDialog,对应Delphi应用程序的某项功能。Delphi应用程序的设计实际上是以窗体的设计为中心。因此,DFM文件在Delphi应用设计中也占很重要的位置。窗体中的所有元素包括窗体自身的属性都包含在DFM文件中。
  在Delphi应用程序窗口中,界面元素是按拥有关系相互联系的,因此树状结构是最自然的表达形式;相应地,窗体中的组件也是按树状结构组织;对应在DFM文件中,也要表达这种关系。DFM文件在物理上,是以文本方式存储的(在Delphi2.0版本以前是存储为二进制文件的),在逻辑上则是以树状结构安排各组件的关系。从该文本中可以看清窗体的树状结构。下面是DFM文件的内容:
object Form1: TForm1
Left = 197
Top = 124
……
PixelsPerInch = 96
TextHeight = 13
object Button1: TButton 
Left = 272
……
Caption = ‘Button1‘
TabOrder = 0
end
object Panel1: TPanel
Left = 120
……
Caption = ‘Panel1‘
TabOrder = 1
object CheckBox1: TCheckBox
Left = 104
……
Caption = ‘CheckBox1‘
TabOrder = 0
end
end
end
这个DFM文件就是TWriter通过流式对象Stream来生成的,当然这里还有一个二进制文件到文本信息文件的转换过程,这个转换过程不是本文要研究的对象,所以忽略这样的一个过程。
在程序开始运行的时候,TReader通过流式对象Stream来读取窗体及组件,因为Delphi在编译程序的时候,利用编译指令{$R *.dfm}已经把DFM文件信息编译到可执行文件中,因此TReader读取的内容实际上是被编译到可执行文件中的有关窗体和组件的信息。
TReader和TWriter不仅能够读取和写入Object Pascal中绝大部分标准数据类型,而且能够读写List、Variant等高级类型,甚至能够读写Perperties和Component。不过,TReader、TWriter自身实际上提供的功能很有限,大部分实际的工作是由TStream这个非常强大的类来完成的。也就是说TReader、TWriter实际上只是一个工具,它只是负责怎么去读写组件,至于具体的读写操作是由TStream来完成的。

由于TFiler是TReader和TWriter的公共祖先类,因为要了解TReader和TWriter,还是先从TFiler开始。
TFiler 
先来看一下TFiler类的定义: 
TFiler = class(TObject) 
  FStream: TStream; 
  FBuffer: Pointer; 
  FBufSize: Integer;  
  FBufPos: Integer; 
  FBufEnd: Integer; 
  FRoot: TComponent; 
  FLookupRoot: TComponent; 
  FAncestor: TPersistent; 
  FIgnoreChildren: Boolean; 
protected 
  procedure SetRoot(Value: TComponent); virtual; 
public 
  constructor Create(Stream: TStream; BufSize: Integer); 
  destructor Destroy; override; 
  procedure DefineProperty(const Name: string; ReadData: TReaderProc; WriteData: TWriterProc; HasData: Boolean); virtual; abstract; 
  procedure DefineBinaryProperty(const Name: string; ReadData, WriteData: TStreamProc; HasData: Boolean); virtual; abstract; 
  procedure FlushBuffer; virtual; abstract; 
  property Root: TComponent read FRoot write SetRoot; 
  property LookupRoot: TComponent read FLookupRoot; 
  property Ancestor: TPersistent read FAncestor write FAncestor; 
  property IgnoreChildren: Boolean read FIgnoreChildren write FIgnoreChildren;
end;
TFiler对象是TReader和TWriter的抽象类,定义了用于组件存储的基本属性和方法。它定义了Root属性,Root指明了所读或写的组件的根对象,它的Create方法将Stream对象作为传入参数以建立与Stream对象的联系, Filer对象的具体读写操作都是由Stream对象完成。因此,只要是Stream对象所能访问的媒介都能由Filer对象存取组件。 
TFiler 对象还提供了两个定义属性的public方法:DefineProperty和DefineBinaryProperty,这两个方法使对象能读写不在组件published部分定义的属性。下面重点介绍一下这两个方法。 
Defineproperty ( )方法用于使标准数据类型持久化,诸如字符串、整数、布尔、字符、浮点和枚举。
在Defineproperty方法中。Name参数用于指定应写入DFM文件的属性的名称,该属性不在类的published部分定义。 
ReadData和WriteData参数指定在存取对象时读和写所需数据的方法。ReadData参数和WriteData参数的类型分别是TReaderProc和TWriterProc。这两个类型是这样声明的: 
TReaderProc = procedure(Reader: TReader) of object; 
TWriterProc = procedure(Writer: TWriter) of object; 
HasData参数在运行时决定了属性是否有数据要存储。 
DefineBinaryProperty方法和Defineproperty有很多的相同之处,它用来存储二进制数据,如声音和图象等。 
下面来说明一下这两个方法的用途。 
我们在窗体上放一个非可视化组件如TTimer,重新打开窗体时我们发现TTimer还是在原来的地方,但TTimer没有Left和Top属性啊,那么它的位置信息保存在哪里呢?
打开该窗体的DFM文件,可以看到有类似如下的几行内容: 
object Timer1: TTimer 
Left = 184 
Top = 149 
end 
Delphi的流系统只能保存published数据,但TTimer并没有published的Left和Top属性,那么这些数据是怎么被保存下来的呢? 
TTimer是TComponent的派生类,在TComponent类中我们发现有这样的一个函数: 
procedure TComponent.DefineProperties(Filer: TFiler); 
var 
  Ancestor: TComponent; 
  Info: Longint; 
begin 
  Info := 0;
  Ancestor := TComponent(Filer.Ancestor); 
  if Ancestor <> nil then Info := Ancestor.FDesignInfo; 
  Filer.DefineProperty(‘Left‘, ReadLeft, WriteLeft, 
  LongRec(FDesignInfo).Lo <> LongRec(Info).Lo); 
  Filer.DefineProperty(‘Top‘, ReadTop, WriteTop, 
  LongRec(FDesignInfo).Hi <> LongRec(Info).Hi); 
end; 
TComponent的DefineProperties是覆盖了它的祖先类TPersistent的方法,在TPersistent类中该方法为空的虚方法。 
在DefineProperties方法中,我们可以看出,有一个Filer对象作为它的参数,当定义属性时,它引用了Ancestor属性,如果该属性非空,对象应当只读写与从Ancestor继承的不同的属性的值。它调用TFiler的DefineProperty方法,并定义了ReadLeft,WriteLeft,ReadTop,WriteTop方法来读写Left和Top属性。
因此,凡是从TComponent派生的组件,即使它没有Left和Top属性,在流化到DFM文件中,都会存在这样的两个属性。 
在查找资料的过程中,发现很少有资料涉及到组件读写机制的。由于组件的写过程是在设计阶段由Delphi的IDE来完成的,因此无法跟踪它的运行过程。所以笔者是通过在程序运行过程中跟踪VCL原代码来了解组件的读机制的,又通过读机制和TWriter来分析组件的写机制。所以下文将按照这一思维过程来讲述组件读写机制,先讲TReader,而后是TWriter。
TReader 
先来看Delphi的工程文件,会发现类似这样的几行代码: 
begin 
Application.Initialize; 
Application.CreateForm(TForm1, Form1); 
Application.Run; 
end. 
这是Delphi程序的入口。简单的说一下这几行代码的意义:Application.Initialize对开始运行的应用程序进行一些必要的初始化工作,Application.CreateForm(TForm1, Form1)创建必要的窗体,Application.Run程序开始运行,进入消息循环。 
现在我们最关心的是创建窗体这一句。窗体以及窗体上的组件是怎么创建出来的呢?在前面已经提到过:窗体中的所有组件包括窗体自身的属性都包含在DFM文件中,而Delphi在编译程序的时候,利用编译指令{$R *.dfm}已经把DFM文件信息编译到可执行文件中。因此,可以断定创建窗体的时候需要去读取DFM信息,用什么去读呢,当然是TReader了! 
通过对程序的一步步的跟踪,可以发现程序在创建窗体的过程中调用了TReader的ReadRootComponent方法。该方法的作用是读出根组件及其所拥有的全部组件。来看一下该方法的实现: 
function TReader.ReadRootComponent(Root: TComponent): TComponent; 
…… 
begin 
ReadSignature; 
Result := nil; 
GlobalNameSpace.BeginWrite; // Loading from stream adds to name space 
try
try
ReadPrefix(Flags, I); 
if Root = nil then 
begin 
Result := TComponentClass(FindClass(ReadStr)).Create(nil); 
Result.Name := ReadStr; 
end else 
begin 
Result := Root; 
ReadStr; { Ignore class name }
if csDesigning in Result.ComponentState then 
ReadStr else 
begin 
Include(Result.FComponentState, csLoading);
Include(Result.FComponentState, csReading); 
Result.Name := FindUniqueName(ReadStr); 
end; 
end; 
FRoot := Result; 
FFinder := TClassFinder.Create(TPersistentClass(Result.ClassType), True); 
try 
FLookupRoot := Result; 
G := GlobalLoaded; 
if G <> nil then 
FLoaded := G else 
FLoaded := TList.Create; 
try 
if FLoaded.IndexOf(FRoot) < 0 then 
FLoaded.Add(FRoot); 
FOwner := FRoot;
Include(FRoot.FComponentState, csLoading);
Include(FRoot.FComponentState, csReading);
FRoot.ReadState(Self); 
Exclude(FRoot.FComponentState, csReading); 
if G = nil then 
for I := 0 to FLoaded.Count - 1 do TComponent(FLoaded[I]).Loaded; 
finally
if G = nil then FLoaded.Free; 
FLoaded := nil; 
end; 
finally 
FFinder.Free; 
end; 
…… 
finally 
GlobalNameSpace.EndWrite; 
end; 
end; 
ReadRootComponent首先调用ReadSignature读取Filer对象标签(’TPF0’)。载入对象之前检测标签,能防止疏忽大意,导致读取无效或过时的数据。
再看一下ReadPrefix(Flags, I)这一句,ReadPrefix方法的功能与ReadSignature的很相象,只不过它是读取流中组件前面的标志(PreFix)。当一个Write对象将组件写入流中时,它在组件前面预写了两个值,第一个值是指明组件是否是从祖先窗体中继承的窗体和它在窗体中的位置是否重要的标志;第二个值指明它在祖先窗体创建次序。 
然后,如果Root参数为nil,则用ReadStr读出的类名创建新组件,并从流中读出组件的Name属性;否则,忽略类名,并判断Name属性的唯一性。 
FRoot.ReadState(Self); 
这是很关键的一句,ReadState方法读取根组件的属性和其拥有的组件。这个ReadState方法虽然是TComponent的方法,但进一步的跟踪就可以发现,它实际上最终还是定位到了TReader的ReadDataInner方法,该方法的实现如下: 
procedure TReader.ReadDataInner(Instance: TComponent); 
var 
OldParent, OldOwner: TComponent; 
begin
while not EndOfList do ReadProperty(Instance); 
ReadListEnd; 
OldParent := Parent; 
OldOwner := Owner; 
Parent := Instance.GetChildParent; 
try 
Owner := Instance.GetChildOwner; 
if not Assigned(Owner) then Owner := Root; 
while not EndOfList do ReadComponent(nil); 
ReadListEnd; 
finally 
Parent := OldParent; 
Owner := OldOwner; 
end; 
end; 
其中有这样的这一行代码: 
while not EndOfList do ReadProperty(Instance); 
这是用来读取根组件的属性的,对于属性,前面提到过,既有组件本身的published属性,也有非published属性,例如TTimer的Left和Top。对于这两种不同的属性,应该有两种不同的读方法,为了验证这个想法,我们来看一下ReadProperty方法的实现。 
procedure TReader.ReadProperty(AInstance: TPersistent); 
…… 
begin 
…… 
PropInfo := GetPropInfo(Instance.ClassInfo, FPropName); 
if PropInfo <> nil then ReadPropValue(Instance, PropInfo) else
begin 
{ Cannot reliably recover from an error in a defined property } 
FCanHandleExcepts := False; 
Instance.DefineProperties(Self); 
FCanHandleExcepts := True; 
if FPropName <> ‘ then 
PropertyError(FPropName); 
end; 
…… 
end; 
为了节省篇幅,省略了一些代码,这里说明一下:FPropName是从文件读取到的属性名。 
PropInfo := GetPropInfo(Instance.ClassInfo, FPropName); 
这一句代码是获得published属性FPropName的信息。从接下来的代码中可以看到,如果属性信息不为空,就通过ReadPropValue方法读取属性值,而ReadPropValue方法是通过RTTI函数来读取属性值的,这里不再详细介绍。如果属性信息为空,说明属性FPropName为非published的,它就必须通过另外一种机制去读取。这就是前面提到的DefineProperties方法,如下: 
Instance.DefineProperties(Self); 
该方法实际上调用的是TReader的DefineProperty方法: 
procedure TReader.DefineProperty(const Name: string; 
ReadData: TReaderProc; WriteData: TWriterProc; HasData: Boolean); 
begin
if SameText(Name, FPropName) and Assigned(ReadData) then 
begin 
ReadData(Self); 
FPropName := ‘; 
end; 
end; 
它先去比较读取的属性名是否和预设的属性名相同,如果相同并且读方法ReadData不为空时就调用ReadData方法读取属性值。 
好了,根组件已经读上来了,接下来应该是读该根组件所拥有的组件了。再来看方法: 
procedure TReader.ReadDataInner(Instance: TComponent); 
该方法后面有一句这样的代码: 
while not EndOfList do ReadComponent(nil); 
这正是用来读取子组件的。子组件的读取机制是和上面所介绍的根组件的读取一样的,这是一个树的深度遍历。 
到这里为止,组件的读机制已经介绍完了。 
再来看组件的写机制。当我们在窗体上添加一个组件时,它的相关的属性就会保存在DFM文件中,这个过程就是由TWriter来完成的。 

TWriter 
TWriter 对象是可实例化的往流中写数据的Filer对象。TWriter对象直接从TFiler继承而来,除了覆盖从TFiler继承的方法外,还增加了大量的关于写各种数据类型(如Integer、String和Component等)的方法。 
TWriter对象提供了许多往流中写各种类型数据的方法, TWrite对象往流中写数据是依据不同的数据采取不同的格式的。 因此要掌握TWriter对象的实现和应用方法,必须了解Writer对象存储数据的格式。 
  首先要说明的是,每个Filer对象的流中都包含有Filer对象标签。该标签占四个字节其值为“TPF0”。Filer对象为WriteSignature和ReadSignature方法存取该标签。该标签主要用于Reader对象读数据(组件等)时,指导读操作。 
  其次,Writer对象在存储数据前都要留一个字节的标志位,以指出后面存放的是什么类型的数据。该字节为TValueType类型的值。TValueType是枚举类型,占一个字节空间,其定义如下: 软件开发网 
  TValueType = (VaNull, VaList, VaInt8, VaInt16, VaInt32, VaEntended, VaString, VaIdent,
VaFalse, VaTrue, VaBinary, VaSet, VaLString, VaNil, VaCollection); 
因此,对Writer对象的每一个写数据方法,在实现上,都要先写标志位再写相应的数据;而Reader对象的每一个读数据方法都要先读标志位进行判断,如果符合就读数据,否则产生一个读数据无效的异常事件。VaList标志有着特殊的用途,它是用来标识后面将有一连串类型相同的项目,而标识连续项目结束的标志是VaNull。因此,在Writer对象写连续若干个相同项目时,先用WriteListBegin写入VaList标志,写完数据项目后,再写出VaNull标志;而读这些数据时,以ReadListBegin开始,ReadListEnd结束,中间用ndofList函数判断是否有VaNull标志。 
来看一下TWriter的一个非常重要的方法WriteData: 
procedure TWriter.WriteData(Instance: TComponent); 
…… 
begin 
……
WritePrefix(Flags, FChildPos);  
if UseQualifiedNames then 
WriteStr(GetTypeData(PTypeInfo(Instance.ClassType.ClassInfo)).UnitName ‘.‘ Instance.ClassName) 软件开发网
else 
WriteStr(Instance.ClassName);
WriteStr(Instance.Name); 
PropertiesPosition := Position; 
if (FAncestorList <> nil) and (FAncestorPos < FAncestorList.Count) then
begin 
if Ancestor <> nil then Inc(FAncestorPos); 
Inc(FChildPos); 
end;
WriteProperties(Instance);  
WriteListEnd; 
…… 
end; 
从WriteData方法中我们可以看出生成DFM文件信息的概貌。先写入组件前面的标志(PreFix),然后写入类名、实例名。紧接着有这样的一条语句: 
WriteProperties(Instance); 
这是用来写组件的属性的。前面提到过,在DFM文件中,既有published属性,又有非published属性,这两种属性的写入方法应该是不一样的。来看WriteProperties的实现:
procedure TWriter.WriteProperties(Instance: TPersistent); 
…… 
begin 
Count := GetTypeData(Instance.ClassInfo)^.PropCount; 
if Count > 0 then  
begin 
GetMem(PropList, Count * SizeOf(Pointer)); 
try 
GetPropInfos(Instance.ClassInfo, PropList); 
for I := 0 to Count - 1 do 
begin 
PropInfo := PropList^[I]; 
if PropInfo = nil then 
Break; 
if IsStoredProp(Instance, PropInfo) then 
WriteProperty(Instance, PropInfo); 
end; 
finally 
FreeMem(PropList, Count * SizeOf(Pointer)); 
end; 
end; 
Instance.DefineProperties(Self); 
end; 
请看下面的代码: 
if IsStoredProp(Instance, PropInfo) then 
WriteProperty(Instance, PropInfo);  
函数IsStoredProp通过存储限定符来判断该属性是否需要保存,如需保存,就调用WriteProperty来保存属性,而WriteProperty是通过一系列的RTTI函数来实现的。
Published属性保存完后就要保存非published属性了,这是通过这句代码完成的: 
Instance.DefineProperties(Self); 
DefineProperties的实现前面已经讲过了,TTimer的Left、Top属性就是通过它来保存的。 
好,到目前为止还存在这样的一个疑问:根组件所拥有的子组件是怎么保存的?再来看WriteData方法(该方法在前面提到过): 
procedure TWriter.WriteData(Instance: TComponent); 
…… 
begin 
…… 
if not IgnoreChildren then  
try 
if (FAncestor <> nil) and (FAncestor is TComponent) then 
begin 
if (FAncestor is TComponent) and (csInline in TComponent(FAncestor).ComponentState) then
FRootAncestor := TComponent(FAncestor); 
FAncestorList := TList.Create; 
TComponent(FAncestor).GetChildren(AddAncestor, FRootAncestor); 
end;
if csInline in Instance.ComponentState then 
FRoot := Instance; 
Instance.GetChildren(WriteComponent, FRoot); 
finally 
FAncestorList.Free; 
end; 
end; 
IgnoreChildren属性使一个Writer对象存储组件时可以不存储该组件拥有的子组件。如果IgnoreChildren属性为True,则Writer对象存储组件时不存它拥有的子组件。否则就要存储子组件。 
Instance.GetChildren(WriteComponent, FRoot);  
这是写子组件的最关键的一句,它把WriteComponent方法作为回调函数,按照深度优先遍历树的原则,如果根组件FRoot存在子组件,则用WriteComponent来保存它的子组件。这样我们在DFM文件中看到的是树状的组件结构。

http://blog.csdn.net/henreash/article/details/7274717

http://blog.csdn.net/henreash/article/details/7280322

以上是关于DELPHI基础教程:开发Delphi对象式数据管理功能(一)[4]的主要内容,如果未能解决你的问题,请参考以下文章

DELPHI基础教程:数据访问部件的应用及编程(一)[1]

DELPHI基础教程:Delphi自定义部件开发(三)[3]

Delphi的组件读写机制

delphi ORACLE三层架构的服务端开发

delphi的简介

delphi的流操作的语法