Ø TReader
先來看Delphi的工程文件,會發(fā)現(xiàn)類似這樣的幾行代碼:
begin
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
這是Delphi程序的入口。簡單的說一下這幾行代碼的意義:Application.Initialize對開始運(yùn)行的應(yīng)用程序進(jìn)行一些必要的初始化工作,Application.CreateForm(TForm1, Form1)創(chuàng)建必要的窗體,Application.Run程序開始運(yùn)行,進(jìn)入消息循環(huán)。
現(xiàn)在我們最關(guān)心的是創(chuàng)建窗體這一句。窗體以及窗體上的
組件是怎么創(chuàng)建出來的呢?在前面已經(jīng)提到過:窗體中的所有組件包括窗體自身的屬性都包含在DFM文件中,而Delphi在編譯程序的時(shí)候,利用編譯指令{$R *.dfm}已經(jīng)把DFM文件信息編譯到可執(zhí)行文件中。因此,可以斷定創(chuàng)建窗體的時(shí)候需要去讀取DFM信息,用什么去讀呢,當(dāng)然是TReader了!
通過對程序的一步步的跟蹤,可以發(fā)現(xiàn)程序在創(chuàng)建窗體的過程中調(diào)用了TReader的ReadRootComponent方法。該方法的作用是讀出根組件及其所擁有的全部組件。來看一下該方法的實(shí)現(xiàn):
function TReader.ReadRootComponent(Root: TComponent): TComponent;
……
begin
ReadSignature;
Result := nil;
GlobalNameSpace.BeginWrite; // Loading from stream adds to name space
try
try
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首先調(diào)用ReadSignature讀取Filer對象標(biāo)簽(’TPF0’)。載入對象之前檢測標(biāo)簽,能防止疏忽大意,導(dǎo)致讀取無效或過時(shí)的數(shù)據(jù)。
再看一下ReadPrefix(Flags, I)這一句,ReadPrefix方法的功能與ReadSignature的很相象,只不過它是讀取流中組件前面的標(biāo)志(PreFix)。當(dāng)一個(gè)Write對象將組件寫入流中時(shí),它在組件前面預(yù)寫了兩個(gè)值,第一個(gè)值是指明組件是否是從祖先窗體中繼承的窗體和它在窗體中的位置是否重要的標(biāo)志;第二個(gè)值指明它在祖先窗體創(chuàng)建次序。
然后,如果Root參數(shù)為nil,則用ReadStr讀出的類名創(chuàng)建新組件,并從流中讀出組件的Name屬性;否則,忽略類名,并判斷Name屬性的唯一性。
FRoot.ReadState(Self);
這是很關(guān)鍵的一句,ReadState方法讀取根組件的屬性和其擁有的組件。這個(gè)ReadState方法雖然是TComponent的方法,但進(jìn)一步的跟蹤就可以發(fā)現(xiàn),它實(shí)際上最終還是定位到了TReader的ReadDataInner方法,該方法的實(shí)現(xiàn)如下:
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。對于這兩種不同的屬性,應(yīng)該有兩種不同的讀方法,為了驗(yàn)證這個(gè)想法,我們來看一下ReadProperty方法的實(shí)現(xiàn)。
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;
為了節(jié)省篇幅,省略了一些代碼,這里說明一下:FPropName是從文件讀取到的屬性名。
PropInfo := GetPropInfo(Instance.ClassInfo, FPropName);
這一句代碼是獲得published屬性FPropName的信息。從接下來的代碼中可以看到,如果屬性信息不為空,就通過ReadPropValue方法讀取屬性值,而ReadPropValue方法是通過RTTI函數(shù)來讀取屬性值的,這里不再詳細(xì)介紹。如果屬性信息為空,說明屬性FPropName為非published的,它就必須通過另外一種機(jī)制去讀取。這就是前面提到的DefineProperties方法,如下:
Instance.DefineProperties(Self);
該方法實(shí)際上調(diào)用的是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;
它先去比較讀取的屬性名是否和預(yù)設(shè)的屬性名相同,如果相同并且讀方法ReadData不為空時(shí)就調(diào)用ReadData方法讀取屬性值。
好了,根組件已經(jīng)讀上來了,接下來應(yīng)該是讀該根組件所擁有的組件了。再來看方法:
procedure TReader.ReadDataInner(Instance: TComponent);
該方法后面有一句這樣的代碼:
while not EndOfList do ReadComponent(nil);
這正是用來讀取子組件的。子組件的讀取機(jī)制是和上面所介紹的根組件的讀取一樣的,這是一個(gè)樹的深度遍歷。
到這里為止,組件的讀機(jī)制已經(jīng)介紹完了。
再來看組件的寫機(jī)制。當(dāng)我們在窗體上添加一個(gè)組件時(shí),它的相關(guān)的屬性就會保存在DFM文件中,這個(gè)過程就是由TWriter來完成的。
Ø TWriter
TWriter 對象是可實(shí)例化的往流中寫數(shù)據(jù)的Filer對象。TWriter對象直接從TFiler繼承而來,除了覆蓋從TFiler繼承的方法外,還增加了大量的關(guān)于寫各種數(shù)據(jù)類型(如Integer、String和Component等)的方法。
TWriter對象提供了許多往流中寫各種類型數(shù)據(jù)的方法, TWrite對象往流中寫數(shù)據(jù)是依據(jù)不同的數(shù)據(jù)采取不同的格式的。 因此要掌握TWriter對象的實(shí)現(xiàn)和應(yīng)用方法,必須了解Writer對象存儲數(shù)據(jù)的格式。
首先要說明的是,每個(gè)Filer對象的流中都包含有Filer對象標(biāo)簽。該標(biāo)簽占四個(gè)字節(jié)其值為“TPF0”。Filer對象為WriteSignature和ReadSignature方法存取該標(biāo)簽。該標(biāo)簽主要用于Reader對象讀數(shù)據(jù)(組件等)時(shí),指導(dǎo)讀操作。
其次,Writer對象在存儲數(shù)據(jù)前都要留一個(gè)字節(jié)的標(biāo)志位,以指出后面存放的是什么類型的數(shù)據(jù)。該字節(jié)為TValueType類型的值。TValueType是枚舉類型,占一個(gè)字節(jié)空間,其定義如下:
TValueType = (VaNull, VaList, VaInt8, VaInt16, VaInt32, VaEntended, VaString, VaIdent,
VaFalse, VaTrue, VaBinary, VaSet, VaLString, VaNil, VaCollection);
因此,對Writer對象的每一個(gè)寫數(shù)據(jù)方法,在實(shí)現(xiàn)上,都要先寫標(biāo)志位再寫相應(yīng)的數(shù)據(jù);而Reader對象的每一個(gè)讀數(shù)據(jù)方法都要先讀標(biāo)志位進(jìn)行判斷,如果符合就讀數(shù)據(jù),否則產(chǎn)生一個(gè)讀數(shù)據(jù)無效的異常事件。VaList標(biāo)志有著特殊的用途,它是用來標(biāo)識后面將有一連串類型相同的項(xiàng)目,而標(biāo)識連續(xù)項(xiàng)目結(jié)束的標(biāo)志是VaNull。因此,在Writer對象寫連續(xù)若干個(gè)相同項(xiàng)目時(shí),先用WriteListBegin寫入VaList標(biāo)志,寫完數(shù)據(jù)項(xiàng)目后,再寫出VaNull標(biāo)志;而讀這些數(shù)據(jù)時(shí),以ReadListBegin開始,ReadListEnd結(jié)束,中間用EndofList函數(shù)判斷是否有VaNull標(biāo)志。
來看一下TWriter的一個(gè)非常重要的方法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文件信息的概貌。先寫入組件前面的標(biāo)志(PreFix),然后寫入類名、實(shí)例名。緊接著有這樣的一條語句:
WriteProperties(Instance);
這是用來寫組件的屬性的。前面提到過,在DFM文件中,既有published屬性,又有非published屬性,這兩種屬性的寫入方法應(yīng)該是不一樣的。來看WriteProperties的實(shí)現(xiàn):
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);
函數(shù)IsStoredProp通過存儲限定符來判斷該屬性是否需要保存,如需保存,就調(diào)用WriteProperty來保存屬性,而WriteProperty是通過一系列的RTTI函數(shù)來實(shí)現(xiàn)的。
Published屬性保存完后就要保存非published屬性了,這是通過這句代碼完成的:
Instance.DefineProperties(Self);
DefineProperties的實(shí)現(xiàn)前面已經(jīng)講過了,TTimer的Left、Top屬性就是通過它來保存的。
好,到目前為止還存在這樣的一個(gè)疑問:根組件所擁有的子組件是怎么保存的?再來看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屬性使一個(gè)Writer對象存儲組件時(shí)可以不存儲該組件擁有的子組件。如果IgnoreChildren屬性為True,則Writer對象存儲組件時(shí)不存它擁有的子組件。否則就要存儲子組件。
Instance.GetChildren(WriteComponent, FRoot);
這是寫子組件的最關(guān)鍵的一句,它把WriteComponent方法作為回調(diào)函數(shù),按照深度優(yōu)先遍歷樹的原則,如果根組件FRoot存在子組件,則用WriteComponent來保存它的子組件。這樣我們在DFM文件中看到的是樹狀的組件結(jié)構(gòu)。