分享
 
 
 

Delphi的组件读写机制(三)

王朝delphi·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

Ø 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结束,中间用EndofList函数判断是否有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文件中看到的是树状的组件结构。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有