[作者:Savetime 轉(zhuǎn)貼自:Delphibbs.com 點(diǎn)擊數(shù):1584 更新時(shí)間:2004-12-28
目 錄
===============================================================================
⊙ DFM 文件與持續(xù)機(jī)制(persistent)
⊙ ReadComponentResFile / WriteComponentResFile 函數(shù)
⊙ Delphi 持續(xù)機(jī)制框架簡述
⊙ 一個(gè) TForm 對(duì)象的創(chuàng)建過程
⊙ TStream Class 和 TStream.ReadComponent 方法
⊙ TReader Class 和 TReader.ReadRootComponent 方法
⊙ TReader.ReadPrefix 方法
⊙ TComponent.ReadState 虛方法
⊙ TReader.ReadData 方法
⊙ TReader.ReadDataInner 方法
⊙ TReader.ReadProperty 方法
⊙ TPersistent.DefineProperties 虛方法
⊙ TReader.ReadComponent 方法
⊙ TReader.ReadValue / TReader.NextValue 系列方法
⊙ TReader.ReadStr 方法
⊙ TReader.ReadInteger / ReadString / ReadBoolean 系列方法
⊙ TReader.Read 方法
⊙ ObjectBinaryToText / ObjectTextToBinary 函數(shù)
===============================================================================
本文排版格式為:
正文由窗口自動(dòng)換行;所有代碼以 80 字符為邊界;中英文字符以空格符分隔。
正 文
===============================================================================
⊙ DFM 文件與持續(xù)機(jī)制(persistent)
===============================================================================
我們?cè)谑褂?Delphi 的 IDE 進(jìn)行快速開發(fā)的時(shí)候,可以方便地從元件面板上拖放元件(component)至表單,完成表單的界面和事件設(shè)計(jì)。Delphi 將這些界面的設(shè)計(jì)期信息保存在表單相應(yīng)的 DFM 文件中,方便程序員隨時(shí)讀取和修改。
DFM 文件根據(jù)元件在表單上的嵌套層次存放元件屬性,以下是一個(gè) DFM 文件的示例:
object Form1: TForm1
...
Left = 192
Top = 107
Width = 544
Caption = 'Form1'
object Button1: TButton
Left = 24
Top = 16
Caption = 'Button1'
onClick = Button1Click
end
...
end
應(yīng)用程序編譯之后,DFM 文件的信息被二進(jìn)制化了,這些二進(jìn)制信息存儲(chǔ)在應(yīng)用程序的資源(resource)段中。每個(gè)表單(也就是 class)及表單上的元件在資源段中存儲(chǔ)為與表單同名的資源,可以使用 FindResource API 獲得。應(yīng)用程序在運(yùn)行期創(chuàng)建表單實(shí)例的時(shí)候,會(huì)從資源段中讀取表單的屬性,還原設(shè)計(jì)期的設(shè)置。這種將類型信息保存在文件中,并且可以在運(yùn)行期恢復(fù)類型的操作,在本文中被稱之為持續(xù)(persistent)機(jī)制。持續(xù)機(jī)制是 Delphi 成為 RAD 工具的原因之一。
持續(xù)機(jī)制和 RTTI 是緊密結(jié)合的,但本文不討論 RTTI(關(guān)于 RTTI 可參考我前幾天寫的兩篇筆記),只討論實(shí)現(xiàn)持續(xù)機(jī)制的總體框架及相關(guān)類(class)。這些類包括 TStream、TFiler、TReader、TWriter、TParser、TPersisetent、TComponent、TCustomForm 等。
===============================================================================
⊙ ReadComponentResFile / WriteComponentResFile 函數(shù)
===============================================================================
讓我們從一個(gè)比較直觀的例子開始。
Classes.pas 中定義了兩個(gè)函數(shù) ReadComponentResFile 和 WriteComponentResFile,它們的功能是“把元件的屬性信息保存到文件”和“從文件中恢復(fù)元件屬性信息”。
先做個(gè)試驗(yàn)。新建一個(gè)項(xiàng)目,在 Form1 上放置兩個(gè) Button 和一個(gè) Memo。Button 的 Click 事件代碼如下。按 F9 運(yùn)行該項(xiàng)目,先在 Memo1 中輸入一些字符,然后按下 Button1,再按下 Button2,你會(huì)看一個(gè)新建的 Form。它的屬性幾乎和 Form1 一樣,甚至連 Memo1 中的字符都保存下來了,唯一的不同只是它的 Name 屬性變成了“Form1_1”。你可以查看 FORM1.RES 文件的內(nèi)容看看 Delphi 是如何存儲(chǔ)元件信息的。
procedure TForm1.Button1Click(Sender: TObject);
begin
WriteComponentResFile('C:\FORM1.RES', Form1);
end;
procedure TForm1.Button2Click(Sender: TObject);
var
NewForm: TForm1;
begin
NewForm := TForm1.CreateNew(Application);
ReadComponentResFile('C:\FORM1.RES', NewForm);
NewForm.Left := NewForm.Left + 100;
end;
WriteComponentResFile 函數(shù)的代碼如下,它只是調(diào)用 Stream 對(duì)象的 WriteComponentRes 方法將對(duì)象屬性保存到資源文件中的:
procedure WriteComponentResFile(const FileName: string; Instance: TComponent);
begin
Stream := TFileStream.Create(FileName, fmCreate);
Stream.WriteComponentRes(Instance.ClassName, Instance);
Stream.Free;
end;
ReadComponentResFile 函數(shù)也是調(diào)用 Stream 的方法實(shí)現(xiàn)從文件中讀取對(duì)屬信息:
function ReadComponentResFile(const FileName: string; Instance: TComponent):
TComponent;
begin
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
Result := Stream.ReadComponentRes(Instance);
Stream.Free;
end;
ReadComponentResFile 函數(shù)可以通過 Instance 參數(shù)傳入對(duì)象句柄,也可以通過返回值獲得對(duì)象句柄。Instance 參數(shù)只能是已實(shí)例化的對(duì)象或 nil。如果是 nil,那么 ReadComponentResFile 會(huì)自動(dòng)根據(jù)文件信息創(chuàng)建對(duì)象實(shí)例,但必須使用 RegisterClass 函數(shù)注冊(cè)將要被載入的類,否則會(huì)觸發(fā)異常。
有個(gè)類似的函數(shù) ReadComponentRes,它從應(yīng)用程序的資源段中恢復(fù)對(duì)象的屬性信息。它的 ResName 參數(shù)就是表單類的名稱:
function ReadComponentRes(const ResName: string; Instance: TComponent):
TComponent;
===============================================================================
⊙ Delphi 持續(xù)機(jī)制框架簡述
===============================================================================
持續(xù)機(jī)制的實(shí)現(xiàn)必須由 IDE、編譯器、表單類、元件類和輔助類合作完成。
這里的表單類不是指一般所指的 TForm class,在 Delphi 的幫助文件中,稱之為“root class”。root class 是指能在設(shè)計(jì)期被 Form Designer 作為最上層編輯表單的類(如 TCustomForm、TFrame、TDataModule 等)。Delphi 在設(shè)計(jì)期將元件的 published 屬性的值保存在 .DFM 文件中,也只有 published 的屬性才能被 Object Insepector 設(shè)置賦值。
Form Designer 設(shè)計(jì)的 root class 對(duì)象在編譯時(shí),Delphi 將對(duì)象的屬性以及其所包含的元件的屬性保存在應(yīng)用程序的資源段(RT_RCDATA)中。
輔助類包括 TStream、TReader、TWriter、TParser 等。這些類起著中間層的作用,用于存儲(chǔ)和讀取對(duì)象屬性的信息。雖然我稱它們?yōu)檩o助類,但是保存和恢復(fù)對(duì)象信息的實(shí)際操作是由它們完成的。
===============================================================================
⊙ 一個(gè) TForm 對(duì)象的創(chuàng)建過程
===============================================================================
下面是一個(gè)典型的表單 Form1 的創(chuàng)建過程,縮進(jìn)代表調(diào)用關(guān)系(Form1.ReadState 例外,防止縮進(jìn)太多),帶“?”的函數(shù)表示我尚未仔細(xì)考察的部分,帶“*”表示元件編寫者需要注意的函數(shù)。
Application.CreateForm(TForm1, Form1);
|-Form1.NewInstance;
|-Form1.Create(Application);
|-Form1.CreateNew(Application);
|-InitInheritedComponent(Form1, TForm);
|-InternalReadComponentRes(Form1.ClassName, Form1ResHInst, Form1);
|-TResourceStream.Create(Form1ResHInst, Form1.ClassName, RT_RCDATA);
|-TResourceStream.ReadComponent(Form1);
|-TReader.Create(ResourceStream, 4096);
|-TReader.ReadRootComponent(Form1);
|-TReader.ReadSignature;
*|-TReader.ReadPrefix(Flags, ChildPos);
|-IF Form1 = nil THEN Form1 := FindClass(ReadStr).Create;
|-Include(Form1.FComponentState, csLoading);
|-Include(Form1.FComponentState, csReading);
|-Form1.Name := FindUniqueName(ReadStr);
|-FFinder := TClassFinder.Create;
*|-Form1.ReadState(Reader);
|-TCustomForm.ReadState(Reader);
{ DisableAlign; }
|-TWinControl.ReadState(Reader);
{ DisableAlign; }
*|-TControl.ReadState(Reader);
{ Include(FControlState, csReadingState); }
{ Parent := TWinControl(Reader.Parent); }
*|-TComponent.ReadState(Reader);
|-Reader.ReadData(Form1);
|-Reader.ReadDataInner(Form1);
|-WHILE NOT EndOfList DO Reader.ReadProperty(Form1);
|-IF PropInfo <> nil THEN ReadPropValue(Form1, PropInfo);
*|-ELSE Form1.DefineProperties(Reader);
|-WHILE NOT EndOfList DO ReadComponent(nil);
|-ReadPrefix(Flags, Position);
|-IF ffInherited THEN FindExistingComponent
|-ELSE CreateComponent;
*|-SubComponent.ReadState(Reader); (Like Form1.ReadState)
|-DoFixupReferences;
過程簡述:
TCustomForm.Create 函數(shù)中先調(diào)用 CreateNew 設(shè)置缺省的表單屬性,然后調(diào)用Classes.InitInheritedComponent 函數(shù)。
InitInheritedComponent 用于初始化一個(gè) root class 對(duì)象。該函數(shù)的功能就是從應(yīng)用程序的資源中恢復(fù)設(shè)計(jì)期的表單信息。InitInheritedComponent 的聲明如下:
{ Classes.pas }
function InitInheritedComponent(Instance: TComponent;
RootAncestor: TClass): Boolean;
InitInheritedComponent 傳入兩個(gè)參數(shù):Instance 參數(shù)代表將要從資源段中恢復(fù)信息的對(duì)象,RootAncestor 表示該對(duì)象的祖先類。如果從資源中恢復(fù)信息成功,則返回 True,否則返回 False。InitInheritedComponent 通常只在 root class 的構(gòu)造函數(shù)中調(diào)用。
constructor TCustomForm.Create(AOwner: TComponent);
begin
...
CreateNew(AOwner); // 初始化缺省的 Form 屬性
Include(FFormState, fsCreating); // 標(biāo)記為 Creating 狀態(tài)
if not InitInheritedComponent(Self, TForm) then // 從資源中恢復(fù) Form 信息
raise EResNotFound.CreateFmt(SResNotFound, [ClassName]);
...
Exclude(FFormState, fsCreating); // 取消 Creating 狀態(tài)
end;
InitInheritedComponent 調(diào)用自身內(nèi)置的函數(shù):InitComponent(Instance.ClassType)。InitComponent 先判斷 Instance.ClassType 是否是 TComponent 或 RootAncestor,如果是則返回 False 并退出,否則調(diào)用 InternalReadComponentRes。
* InitComponent 遞歸調(diào)用自己檢查類信息。沒看懂為什么要這樣設(shè)計(jì),如果有誰看懂了請(qǐng)告訴我。
function InitComponent(ClassType: TClass): Boolean;
begin
Result := False;
if (ClassType = TComponent) or (ClassType = RootAncestor) then Exit;
Result := InitComponent(ClassType.ClassParent);
Result := InternalReadComponentRes(ClassType.ClassName,
FindResourceHInstance(FindClassHInstance(ClassType)), Instance) or Result;
end;
InternalReadComponentRes 使用 Instance.ClassName 作為 ResourceName,調(diào)用 FindResourceHInstance 找到 class 資源所在模塊的 HInst 句柄(因?yàn)?class 可能是在動(dòng)態(tài)鏈接庫中),并通過引用方式傳遞 Instance 對(duì)象(* 好像沒有必要使用引用方式,InitInheritedComponent 也沒有使用引用方式):
{ Classes.pas }
function InternalReadComponentRes(const ResName: string; HInst: THandle;
var Instance: TComponent): Boolean;
InternalReadComponentRes 先檢查 class 資源是否存在,如果存在則創(chuàng)建一個(gè) TResourceStream 對(duì)象(TResourceStream 的 Create 構(gòu)造函數(shù)把 class 信息的資源內(nèi)存地址和大小記錄在成員字段中),然后使用 TResourceStream.ReadComponent 方法從資源中讀取 Instance 的信息。TResourceStream 并沒有定義 ReadComponent 方法,而是使用祖先類 TStream 的方法。TStream.ReadComponent 創(chuàng)建一個(gè) TReader 對(duì)象,然后使用自己的對(duì)象地址(Self)作為參數(shù),調(diào)用 TReader.ReadRootComponent 讀取 Instance 對(duì)象的內(nèi)容。
{ TReader }
function ReadRootComponent(Root: TComponent): TComponent;
ReadRootComponent 先調(diào)用 TReader.ReadSignature。ReadSignature 從 stream 中讀取 4 字節(jié)的內(nèi)容,如果讀出來的內(nèi)容不是 'TPF0',則觸發(fā)異常(SInvalidImage),表示該 stream 的內(nèi)容是錯(cuò)誤的。然后 ReadRootComponent 調(diào)用 ReadPrefix 讀取元件的狀態(tài)信息。
如果 Root 參數(shù)是 nil,也就是說 Root 對(duì)象還沒被創(chuàng)建,則直接從流中讀取 Root 的類名,再使用 FindClass 函數(shù)找到該類在內(nèi)存中的地址,并調(diào)用該類的構(gòu)造函數(shù)創(chuàng)建 Root 的實(shí)例。
接下來 ReadRootComponent 調(diào)用 Root 的 ReadState 虛函數(shù)從流中讀取 Root 對(duì)象的屬性。TComponent.ReadState 只有一行代碼:Reader.ReadData(Self);。
ReadData 調(diào)用 ReadDataInner 讀取 root 元件及 root 的子元件的屬性信息。
ReadDataInner 先循環(huán)調(diào)用 ReadProperty 從流中讀取 root 元件的屬性,直到遇到 EndOfList 標(biāo)志(vaNull)。ReadProperty 使用 RTTI 函數(shù),將從流中讀出的數(shù)據(jù)設(shè)置為對(duì)象的屬性。ReadProperty 中還調(diào)用了 Instance.DefineProperties,用于實(shí)現(xiàn)自定義的屬性存儲(chǔ)。ReadDataInner 然后循環(huán)調(diào)用 ReadComponent(nil) 讀取子元件的信息。
ReadComponent 的執(zhí)行過程與 ReadRootComponent 的過程很相似,它根據(jù)流中的信息使用 FindComponentClass 找到元件類在內(nèi)存中的地址,然后調(diào)用該元件類的構(gòu)造函數(shù)創(chuàng)建對(duì)象,接下來調(diào)用新建對(duì)象的 ReadState -> TReader.ReadData -> ReadDataInner -> TReader.ReadProperty,重復(fù) ReadRootComponent 的過程。
TReader.ReadComponent 和 TComponent.ReadState 形成遞歸調(diào)用過程,把表單上嵌套的元件創(chuàng)建出來。
最后 InitInheritedComponent 函數(shù)返回,一個(gè) root class 對(duì)象從資源中實(shí)例化的過程完成。
===============================================================================
⊙ TStream Class 和 TStream.ReadComponent 方法
===============================================================================
TStream 在對(duì)象持續(xù)機(jī)制扮演的角色是提供一種存儲(chǔ)媒介,由 TFiler 對(duì)象使用。TStream 是一個(gè)虛類,它定義了數(shù)據(jù)的“流式”讀寫方法。它的繼承類 TFileStream、TMemoryStream、TResourceStream 等實(shí)現(xiàn)對(duì)不同媒體的讀寫。對(duì)象的 persistent 信息可以存儲(chǔ)在任何 TStream 類中,也可以從任何 TStream 中獲得。由于 Delphi 缺省的對(duì)象信息存儲(chǔ)在應(yīng)用程序的資源段中,因此,可以從程序的資源段中讀取數(shù)據(jù)的 TResourceStream 類就顯得更加重要。
TStream 定義兩個(gè)讀寫緩沖的方法:ReadBuffer 和 WriteBuffer。這兩個(gè)方法封裝了 TStream.Read 和 TStream.Write 純虛方法(必須被后繼類重載)。
{ TStream }
procedure ReadBuffer(var Buffer; Count: Longint);
procedure WriteBuffer(const Buffer; Count: Longint);
可以看到這兩個(gè)方法的 Buffer 參數(shù)都是無類型的,也就是使用引用的方式傳入的,所以不管是使用單個(gè)字符或自定義的結(jié)構(gòu)都是正確的(當(dāng)然,不能使用常量)。Count 指示要讀或?qū)懭氲?Buffer 的大小(Bytes)。
TStream 還定義了兩個(gè)元件信息的讀寫方法:ReadComponent 和 WriteComponent。由于 WriteComponent 通常是由 Delphi 的 IDE/編譯器調(diào)用的,很難跟蹤它的執(zhí)行過程,所以我們以后主要考察 ReadComponent 方法。我們可以很容易想像這兩個(gè)方法互為逆過程,理解了其中一個(gè)也就能知道另一個(gè)所做的工作。
{ TStream }
function ReadComponent(Instance: TComponent): TComponent;
procedure WriteComponent(Instance: TComponent);
TStream.ReadComponent 創(chuàng)建了一個(gè) TReader 對(duì)象,將自己的對(duì)象地址作為參數(shù)傳遞給 Reader,并調(diào)用 Reader.ReadRootComponent 創(chuàng)建對(duì)象實(shí)例。
function TStream.ReadComponent(Instance: TComponent): TComponent;
var
Reader: TReader;
begin
Reader := TReader.Create(Self, 4096); // 4096 是緩沖區(qū)大小
Result := Reader.ReadRootComponent(Instance);
Reader.Free;
end;
TStream 把自己的對(duì)象句柄交給 TReader 之后,就成了 TReader 讀取對(duì)象屬性資料的來源。此后 TStream 對(duì)象只由 TReader 來掌控,自己不再主動(dòng)進(jìn)行其它工作。
===============================================================================
⊙ TReader Class 和 TReader.ReadRootComponent 方法
===============================================================================
TReader 和 TWriter 都是從 TFiler 繼承下來的類。TFiler 是個(gè)純虛類,它的構(gòu)造函數(shù)被 TReader 和 TWrite 共享。TFiler.Create 先把 Stream 參數(shù)保存在 FStream 字段中,然后生成一個(gè)自己的緩沖區(qū):
constructor TFiler.Create(Stream: TStream; BufSize: Integer);
begin
FStream := Stream; // 保存 stream 對(duì)象
GetMem(FBuffer, BufSize); // 創(chuàng)建自己的緩沖區(qū),加速數(shù)據(jù)訪問
FBufSize := BufSize; // 設(shè)置緩沖區(qū)大小
end;
上面說到 TStream.ReadComponent 在創(chuàng)建 TReader 對(duì)象之后,立即調(diào)用 TReader.ReadRootComponent 方法。TReader.ReadRootComponent 方法的功能是從 stream 中讀取 root class 對(duì)象的屬性。并返回該對(duì)象的指針。
{ TReader }
function ReadRootComponent(Root: TComponent): TComponent;
ReadRootComponent 先調(diào)用 TReader.ReadSignature。
TReader.ReadSignature 方法從 stream 中讀取 4 字節(jié)的內(nèi)容,如果讀出來的內(nèi)容不是 'TPF0',則觸發(fā)異常(SInvalidImage),表示該 stream 的內(nèi)容是錯(cuò)誤的。'TPF0' 就是 root class 對(duì)象的標(biāo)記。
然后 ReadRootComponent 調(diào)用 ReadPrefix 讀取元件的繼承信息。
如果 Root 參數(shù)是 nil,也就是說 Root 對(duì)象還沒被創(chuàng)建,則直接從流中讀取 Root 的類名,再使用 FindClass 函數(shù)找到該類在內(nèi)存中的地址,并調(diào)用該類的構(gòu)造函數(shù)創(chuàng)建 Root 的實(shí)例。如果 Root 實(shí)例已存在,則調(diào)用內(nèi)嵌的 FindUniquName 函數(shù)檢查 Root.Name 是否與已有的實(shí)例重復(fù),如有重復(fù)則在 Root.Name 后加上序號(hào)使其唯一。
接下來 ReadRootComponent 調(diào)用 Root 的 ReadState 虛方法從流中讀取 Root 對(duì)象的屬性。
===============================================================================
⊙ TReader.ReadPrefix 方法
===============================================================================
ReadPrefix 方法用于讀取元件的狀態(tài)信息,這些信息是由 Writer 在寫入元件屬性之前寫入的。
{ TReader }
procedure ReadPrefix(var Flags: TFilerFlags; var AChildPos: Integer); virtual;
Flags 參數(shù)是以引用方式傳遞的,用于設(shè)置元件的在表單中的狀態(tài),元件的狀態(tài)在這里包含三種情況:
ffInherited:表示元件存在于表單的父類之中
ffChildPos :表示元件在表單中的創(chuàng)建次序(creation order)是重要的
ffInline :表示元件是最上級(jí)(top-level)的元件,比如表單或數(shù)據(jù)模塊
如果元件的狀態(tài)中包含 ffChildPos,ReadPrefix 還會(huì)讀取元件的創(chuàng)建次序值,存放在 AChildPos 參數(shù)中。
===============================================================================
⊙ TComponent.ReadState 虛方法
===============================================================================
設(shè)置 ReadState 方法的主要目的是在讀取屬性信息的前后可以讓元件進(jìn)行一些處理工作。ReadState 是 Component Writer 需要注意的方法。
{ TComponent }
procedure ReadState(Reader: TReader); virtual;
由于 ReadState 是虛函數(shù),在 TControl、TWinControl、TCustomForm 等后續(xù)類中都被重載,進(jìn)行自己需要的操作(比如 DisableAlign、UpdateControlState)。
TComponent.ReadState 只有一行代碼:Reader.ReadData(Self);
注意:自己重載 ReadState 方法必須調(diào)用 inherited 。
===============================================================================
⊙ TReader.ReadData 方法
===============================================================================
上面說到 TComponent.ReadState 又回頭調(diào)用 TReader.ReadData 方法。它的主要代碼如下:
{ TReader }
procedure TReader.ReadData(Instance: TComponent);
begin
...
ReadDataInner(Instance);
DoFixupReferences;
...
end;
TReader.ReadData 基本上是個(gè)包裝函數(shù),它調(diào)用 TReader.ReadDataInner 讀取 root 對(duì)象及 root 所包含的元件的屬性信息。
===============================================================================
⊙ TReader.ReadDataInner 方法
===============================================================================
ReadDataInner 負(fù)責(zé)讀取元件的屬性和子元件的屬性,它的主要代碼如下:
procedure TReader.ReadDataInner(Instance: TComponent);
begin
...
while not EndOfList do ReadProperty(Instance);
...
while not EndOfList do ReadComponent(nil);
...
end;
ReadDataInner 先循環(huán)調(diào)用 ReadProperty 從流中讀取對(duì)象的屬性,直到遇到 EndOfList 標(biāo)志(vaNull)。再循環(huán)調(diào)用 ReadComponent(nil) 讀取子元件的信息。這兩個(gè)方法都是 TReader 的重要方法,后面分兩節(jié)討論。ReadDataInner 在ReadProperty 調(diào)用之后還設(shè)置了元件的 Parent 和 Owner 關(guān)系。
===============================================================================
⊙ TReader.ReadProperty 方法
===============================================================================
ReadProperty 使用 RTTI 函數(shù)將從流中讀出的數(shù)據(jù)設(shè)置為對(duì)象的屬性。它先解析從流中讀出的屬性名稱,然后判斷該屬性是否有 RTTI 信息,如果有則調(diào)用 TReader.ReadPropValue 方法從流中讀取屬性值;如果該屬性沒有 RTTI 信息,說明該屬性不屬于 published 段,而是由元件自己寫入的,因此調(diào)用 TPersistent.DefineProperties 讀取自定義的元件信息。ReadProperty 的關(guān)鍵代碼:
procedure TReader.ReadProperty(AInstance: TPersistent);
begin
...
PropInfo := GetPropInfo(Instance.ClassInfo, FPropName);
if PropInfo <> nil then // 檢查屬性 RTTI 信息
ReadPropValue(Instance, PropInfo) // 從流中讀取屬性
else begin
Instance.DefineProperties(Self); // 調(diào)用自定義存儲(chǔ)過程
if FPropName <> '' then PropertyError(FPropName); // 注意這里
end;
...
end;
ReadPropValue 方法基本上是使用 SetOrdProp、SetFloatProp、SetStrProp、GetEnumValue 等 RTTI 函數(shù)設(shè)置元件的屬性值,它的代碼冗長而簡單,不再單獨(dú)列出。下面介紹比較重要的 DefineProperties 函數(shù)。
===============================================================================
⊙ TPersistent.DefineProperties 虛方法
===============================================================================
DefineProperties 虛方法用于元件設(shè)計(jì)者自定義非 published 屬性的存儲(chǔ)和讀取方法。 TPersistent 定義的該方法是個(gè)空方法,到 TComponent 之后被重載。
procedure TPersistent.DefineProperties(Filer: TFiler); virtual;
下面以 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;
DefineProperties 調(diào)用 Filer.DefineProperty 或 DefineBinaryProperty 方法讀寫流中屬性值。
TReader.DefineProperty 方法檢查傳入的屬性名稱是否與當(dāng)前流中讀到的屬性名稱相同,如果相同,則調(diào)用傳入的 ReadData 方法讀取數(shù)據(jù),并設(shè)置 FPropName 為空,用以通知 ReadProperty 已經(jīng)完成讀屬性值的工作,否則將會(huì)觸發(fā)異常。
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;
TWriter.DefineProperty 根據(jù) HasData 參數(shù)決定是否需要寫屬性值。
procedure TWriter.DefineProperty(const Name: string;
ReadData: TReaderProc; WriteData: TWriterProc; HasData: Boolean);
begin
if HasData and Assigned(WriteData) then
begin
WritePropName(Name);
WriteData(Self);
end;
end;
如果 Filer.Ancestor 不是 nil,表示當(dāng)前正在讀取的元件繼承自表單父類中的元件,元件設(shè)計(jì)者可以根據(jù) Ancestor 判斷是否需要寫屬性至流中。例如:當(dāng)前元件的屬性值與原表單類中的元件屬性值相同的時(shí)候,可以不寫入(通常是這樣設(shè)計(jì))。
ReadData、WriteData 參數(shù)是從 Filer 對(duì)象中讀寫數(shù)據(jù)的方法地址,它們的類型是:
TReaderProc = procedure(Reader: TReader) of object;
TWriterProc = procedure(Writer: TWriter) of object;
比如:
procedure TComponent.ReadLeft(Reader: TReader);
begin
LongRec(FDesignInfo).Lo := Reader.ReadInteger;
end;
procedure TComponent.WriteLeft(Writer: TWriter);
begin
Writer.WriteInteger(LongRec(FDesignInfo).Lo);
end;
對(duì)于二進(jìn)制格式的屬性值,可以使用 TFiler.DefineBinaryProperty 方法讀寫:
procedure DefineBinaryProperty(const Name: string;
ReadData, WriteData: TStreamProc; HasData: Boolean); override;
TStreamProc = procedure(Stream: TStream) of object;
Stream 參數(shù)是從流中讀出的二進(jìn)制數(shù)據(jù)或要寫入二進(jìn)制數(shù)據(jù)的流對(duì)象句柄。
注意:自己定義屬性的讀寫方法時(shí)要記得調(diào)用 inherited DefineProperties(Filer),否則祖先類的自定義屬性讀寫操作不會(huì)進(jìn)行。TControl 是個(gè)例外,因?yàn)樗呀?jīng)定義了 published Left 和 Top 屬性。
===============================================================================
⊙ TReader.ReadComponent 方法
===============================================================================
ReadComponent 的執(zhí)行過程與 ReadRootComponent 的過程很相似,它根據(jù)流中的信息使用 FindComponentClass 方法找到元件類在內(nèi)存中的地址,然后調(diào)用該元件類的構(gòu)造函數(shù)創(chuàng)建對(duì)象,接下來調(diào)用新建對(duì)象的 ReadState -> TReader.ReadData -> ReadDataInner -> TReader.ReadProperty,重復(fù) ReadRootComponent 的過程。
{ TReader }
function ReadComponent(Component: TComponent): TComponent;
TReader.ReadComponent 和 TComponent.ReadState 形成遞歸調(diào)用過程,把表單上嵌套的元件創(chuàng)建出來。
===============================================================================
⊙ TReader.ReadValue / TReader.NextValue 系列方法
===============================================================================
ReadValue 方法從流中讀出一個(gè) TValueType 類型的數(shù)據(jù),它主要由其它的方法調(diào)用。
TValueType 中只有 vaList 比較特殊,它表示后面的數(shù)據(jù)是一個(gè)屬性值系列,以 vaNull 結(jié)束。其余的枚舉值的都是指屬性的數(shù)據(jù)類型或值。
TValueType = (vaNull, vaList, vaInt8, vaInt16, vaInt32, vaExtended,
vaString, vaIdent, vaFalse, vaTrue, vaBinary, vaSet, vaLString,
vaNil, vaCollection, vaSingle, vaCurrency, vaDate, vaWString,
vaInt64, vaUTF8String);
function TReader.ReadValue: TValueType;
begin
Read(Result, SizeOf(Result));
end;
NextValue 方法調(diào)用 ReadValue 返回流中下一個(gè)數(shù)據(jù)的類型,然后將流指針回退至讀數(shù)據(jù)之前。通常用于檢測(cè)流中下一個(gè)數(shù)據(jù)的類型。
function TReader.NextValue: TValueType;
begin
Result := ReadValue;
Dec(FBufPos);
end;
CheckValue 方法調(diào)用 ReadValue 檢查下一個(gè)數(shù)據(jù)類型是否是指定的類型,如果不是則觸發(fā)異常。
ReadListBegin 方法檢查下一個(gè)數(shù)據(jù)是否是 vaList,它調(diào)用 CheckValue 方法。
ReadListEnd 方法檢查下一個(gè)數(shù)據(jù)是否是 vaNull,它調(diào)用 CheckValue 方法。
SkipValue 方法使用 ReadValue 獲得下一個(gè)數(shù)據(jù)的類型,然后將流指針跳過這個(gè)數(shù)據(jù)。
===============================================================================
⊙ TReader.ReadStr 方法
===============================================================================
ReadStr 方法讀出流中的短字符串,TReader 內(nèi)部使用它讀取屬性名稱等字符串,元件設(shè)計(jì)者應(yīng)該使用 ReadString 函數(shù)讀取屬性值。
function ReadStr: string;
===============================================================================
⊙ TReader.ReadInteger / ReadString / ReadBoolean 系列方法
===============================================================================
TReader 有一系列讀取屬性值的函數(shù),可供元件設(shè)計(jì)者使用。
function ReadInteger: Longint;
function ReadInt64: Int64;
function ReadBoolean: Boolean;
function ReadChar: Char;
procedure ReadCollection(Collection: TCollection);
function ReadFloat: Extended;
function ReadSingle: Single;
function ReadCurrency: Currency;
function ReadDate: TDateTime;
function ReadIdent: string;
function ReadString: string;
function ReadWideString: WideString;
function ReadVariant: Variant;
===============================================================================
⊙ TReader.Read 方法
===============================================================================
TReader 中所有的數(shù)據(jù)都是通過 TReader.Read 方法讀取的。TReader 不直接調(diào)用 TStream 的讀方法是因?yàn)?TReader 的讀數(shù)據(jù)操作很頻繁,它自己建立了一個(gè)緩沖區(qū)(4K),只有當(dāng)緩沖區(qū)中的數(shù)據(jù)讀完之后才會(huì)調(diào)用 TStream.Read 再讀入下一段數(shù)據(jù),這樣可以極大地加快讀取速度。Read 是個(gè)匯編函數(shù),編寫得很巧妙,它的代碼及注釋如下:
procedure TReader.Read(var Buf; Count: Longint); assembler;
asm
PUSH ESI
PUSH EDI
PUSH EBX
MOV EDI,EDX ; EDI <- @Buf
MOV EBX,ECX ; EBX <- Count
MOV ESI,EAX ; ESI <- Self
JMP @@6 ; check if Count = 0
{ @@1: 檢查 TReader 的緩沖數(shù)據(jù)是否用盡 }
@@1: MOV ECX,[ESI].TReader.FBufEnd ; ECX <- FBufEnd
SUB ECX,[ESI].TReader.FBufPos ; if FBufEnd > FBufPos jmp @@2
JA @@2
MOV EAX,ESI ; else EAX <- Self
CALL TReader.ReadBuffer ; call ReadBuffer
MOV ECX,[ESI].TReader.FBufEnd ; ECX <- FBufEnd
{ @@2: 檢查要讀出的數(shù)量是否超過緩沖區(qū)大小,如是則分批讀取 }
@@2: CMP ECX,EBX ; if FBufEnd < Count jmp @@3
JB @@3
MOV ECX,EBX ; else ECX <- Count
{ @@3: 分批讀取緩沖區(qū) }
@@3: PUSH ESI
SUB EBX,ECX ; Count = Count - FBufEnd
MOV EAX,[ESI].TReader.FBuffer ; EAX <- FBuffer
ADD EAX,[ESI].TReader.FBufPos ; EAX = FBuffer + FBufPos
ADD [ESI].TReader.FBufPos,ECX ; FBufPos = FBufPos + FBufEnd
MOV ESI,EAX ; ESI <- Curr FBuffer Addr
MOV EDX,ECX ; EDX <- FBufEnd
SHR ECX,2 ; ECX <- FBufEnd / 4
CLD
REP MOVSD ; Copy Buffer
MOV ECX,EDX ; ECX <- FBufEnd
AND ECX,3 ; Check if FBufEnd Loss 3
REP MOVSB ; Copy left Buff
POP ESI ; ESI <- Self
{ @@6: 檢查是否讀完數(shù)據(jù),然后重復(fù) @@1 或退出 }
@@6: OR EBX,EBX ; if Count = 0 then Exit
JNE @@1 ; Repeat ReadBuffer
POP EBX
POP EDI
POP ESI
end;
===============================================================================
⊙ ObjectBinaryToText / ObjectTextToBinary 函數(shù)
===============================================================================
Classes.pas 中的 ObjectBinaryToText 和 ObjectTextToBinary 函數(shù)用于把對(duì)象屬性信息轉(zhuǎn)換為文本形式或二進(jìn)制形式。
procedure ObjectBinaryToText(Input, Output: TStream);
procedure ObjectTextToBinary(Input, Output: TStream);
新建一個(gè)項(xiàng)目,在表單上放置一個(gè) TMemo 控件,然后執(zhí)行以下代碼,就能明白這兩個(gè)函數(shù)的作用了。在 Delphi 的 IDE 中,將 DFM 文件進(jìn)行二進(jìn)制和文本方式的轉(zhuǎn)換應(yīng)該是通過這兩個(gè)函數(shù)進(jìn)行的。
var
InStream, OutStream: TMemoryStream;
begin
InStream := TMemoryStream.Create;
OutStream := TMemoryStream.Create;
InStream.WriteComponent(Self);
InStream.Seek(0, soFromBeginning);
ObjectBinaryToText(InStream, OutStream);
OutStream.Seek(0, soFromBeginning);
Memo1.Lines.LoadFromStream(OutStream);
end;
上面的兩個(gè)函數(shù)還有一對(duì)增強(qiáng)版本,它們?cè)黾恿藢?duì)資源文件格式的轉(zhuǎn)換,實(shí)際上也是調(diào)用了上面的函數(shù):
procedure ObjectResourceToText(Input, Output: TStream);
procedure ObjectTextToResource(Input, Output: TStream);
Delphi 編譯程序生成應(yīng)用程序的資源數(shù)據(jù)段,應(yīng)該是用 ObjectTextToResource 函數(shù)進(jìn)行的。
注:ObjectTextToBinary 調(diào)用了 TParser 對(duì)象進(jìn)行字符串解析工作。
===============================================================================
⊙ 結(jié)束
===============================================================================