[作者:Savetime 轉(zhuǎn)貼自:Delphibbs.com 點擊數(shù):1584 更新時間:2004-12-28
目 錄
===============================================================================
⊙ DFM 文件與持續(xù)機制(persistent)
⊙ ReadComponentResFile / WriteComponentResFile 函數(shù)
⊙ Delphi 持續(xù)機制框架簡述
⊙ 一個 TForm 對象的創(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ù)
===============================================================================
本文排版格式為:
正文由窗口自動換行;所有代碼以 80 字符為邊界;中英文字符以空格符分隔。
正 文
===============================================================================
⊙ DFM 文件與持續(xù)機制(persistent)
===============================================================================
我們在使用 Delphi 的 IDE 進行快速開發(fā)的時候,可以方便地從元件面板上拖放元件(component)至表單,完成表單的界面和事件設(shè)計。Delphi 將這些界面的設(shè)計期信息保存在表單相應(yīng)的 DFM 文件中,方便程序員隨時讀取和修改。
DFM 文件根據(jù)元件在表單上的嵌套層次存放元件屬性,以下是一個 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 文件的信息被二進制化了,這些二進制信息存儲在應(yīng)用程序的資源(resource)段中。每個表單(也就是 class)及表單上的元件在資源段中存儲為與表單同名的資源,可以使用 FindResource API 獲得。應(yīng)用程序在運行期創(chuàng)建表單實例的時候,會從資源段中讀取表單的屬性,還原設(shè)計期的設(shè)置。這種將類型信息保存在文件中,并且可以在運行期恢復(fù)類型的操作,在本文中被稱之為持續(xù)(persistent)機制。持續(xù)機制是 Delphi 成為 RAD 工具的原因之一。
持續(xù)機制和 RTTI 是緊密結(jié)合的,但本文不討論 RTTI(關(guān)于 RTTI 可參考我前幾天寫的兩篇筆記),只討論實現(xiàn)持續(xù)機制的總體框架及相關(guān)類(class)。這些類包括 TStream、TFiler、TReader、TWriter、TParser、TPersisetent、TComponent、TCustomForm 等。
===============================================================================
⊙ ReadComponentResFile / WriteComponentResFile 函數(shù)
===============================================================================
讓我們從一個比較直觀的例子開始。
Classes.pas 中定義了兩個函數(shù) ReadComponentResFile 和 WriteComponentResFile,它們的功能是“把元件的屬性信息保存到文件”和“從文件中恢復(fù)元件屬性信息”。
先做個試驗。新建一個項目,在 Form1 上放置兩個 Button 和一個 Memo。Button 的 Click 事件代碼如下。按 F9 運行該項目,先在 Memo1 中輸入一些字符,然后按下 Button1,再按下 Button2,你會看一個新建的 Form。它的屬性幾乎和 Form1 一樣,甚至連 Memo1 中的字符都保存下來了,唯一的不同只是它的 Name 屬性變成了“Form1_1”。你可以查看 FORM1.RES 文件的內(nèi)容看看 Delphi 是如何存儲元件信息的。
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 對象的 WriteComponentRes 方法將對象屬性保存到資源文件中的:
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 的方法實現(xiàn)從文件中讀取對屬信息:
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ù)傳入對象句柄,也可以通過返回值獲得對象句柄。Instance 參數(shù)只能是已實例化的對象或 nil。如果是 nil,那么 ReadComponentResFile 會自動根據(jù)文件信息創(chuàng)建對象實例,但必須使用 RegisterClass 函數(shù)注冊將要被載入的類,否則會觸發(fā)異常。
有個類似的函數(shù) ReadComponentRes,它從應(yīng)用程序的資源段中恢復(fù)對象的屬性信息。它的 ResName 參數(shù)就是表單類的名稱:
function ReadComponentRes(const ResName: string; Instance: TComponent):
TComponent;
===============================================================================
⊙ Delphi 持續(xù)機制框架簡述
===============================================================================
持續(xù)機制的實現(xiàn)必須由 IDE、編譯器、表單類、元件類和輔助類合作完成。
這里的表單類不是指一般所指的 TForm class,在 Delphi 的幫助文件中,稱之為“root class”。root class 是指能在設(shè)計期被 Form Designer 作為最上層編輯表單的類(如 TCustomForm、TFrame、TDataModule 等)。Delphi 在設(shè)計期將元件的 published 屬性的值保存在 .DFM 文件中,也只有 published 的屬性才能被 Object Insepector 設(shè)置賦值。
Form Designer 設(shè)計的 root class 對象在編譯時,Delphi 將對象的屬性以及其所包含的元件的屬性保存在應(yīng)用程序的資源段(RT_RCDATA)中。
輔助類包括 TStream、TReader、TWriter、TParser 等。這些類起著中間層的作用,用于存儲和讀取對象屬性的信息。雖然我稱它們?yōu)檩o助類,但是保存和恢復(fù)對象信息的實際操作是由它們完成的。
===============================================================================
⊙ 一個 TForm 對象的創(chuàng)建過程
===============================================================================
下面是一個典型的表單 Form1 的創(chuàng)建過程,縮進代表調(diào)用關(guān)系(Form1.ReadState 例外,防止縮進太多),帶“?”的函數(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 用于初始化一個 root class 對象。該函數(shù)的功能就是從應(yīng)用程序的資源中恢復(fù)設(shè)計期的表單信息。InitInheritedComponent 的聲明如下:
{ Classes.pas }
function InitInheritedComponent(Instance: TComponent;
RootAncestor: TClass): Boolean;
InitInheritedComponent 傳入兩個參數(shù):Instance 參數(shù)代表將要從資源段中恢復(fù)信息的對象,RootAncestor 表示該對象的祖先類。如果從資源中恢復(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è)計,如果有誰看懂了請告訴我。
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 句柄(因為 class 可能是在動態(tài)鏈接庫中),并通過引用方式傳遞 Instance 對象(* 好像沒有必要使用引用方式,InitInheritedComponent 也沒有使用引用方式):
{ Classes.pas }
function InternalReadComponentRes(const ResName: string; HInst: THandle;
var Instance: TComponent): Boolean;
InternalReadComponentRes 先檢查 class 資源是否存在,如果存在則創(chuàng)建一個 TResourceStream 對象(TResourceStream 的 Create 構(gòu)造函數(shù)把 class 信息的資源內(nèi)存地址和大小記錄在成員字段中),然后使用 TResourceStream.ReadComponent 方法從資源中讀取 Instance 的信息。TResourceStream 并沒有定義 ReadComponent 方法,而是使用祖先類 TStream 的方法。TStream.ReadComponent 創(chuàng)建一個 TReader 對象,然后使用自己的對象地址(Self)作為參數(shù),調(diào)用 TReader.ReadRootComponent 讀取 Instance 對象的內(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)容是錯誤的。然后 ReadRootComponent 調(diào)用 ReadPrefix 讀取元件的狀態(tài)信息。
如果 Root 參數(shù)是 nil,也就是說 Root 對象還沒被創(chuàng)建,則直接從流中讀取 Root 的類名,再使用 FindClass 函數(shù)找到該類在內(nèi)存中的地址,并調(diào)用該類的構(gòu)造函數(shù)創(chuàng)建 Root 的實例。
接下來 ReadRootComponent 調(diào)用 Root 的 ReadState 虛函數(shù)從流中讀取 Root 對象的屬性。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è)置為對象的屬性。ReadProperty 中還調(diào)用了 Instance.DefineProperties,用于實現(xiàn)自定義的屬性存儲。ReadDataInner 然后循環(huán)調(diào)用 ReadComponent(nil) 讀取子元件的信息。
ReadComponent 的執(zhí)行過程與 ReadRootComponent 的過程很相似,它根據(jù)流中的信息使用 FindComponentClass 找到元件類在內(nèi)存中的地址,然后調(diào)用該元件類的構(gòu)造函數(shù)創(chuàng)建對象,接下來調(diào)用新建對象的 ReadState -> TReader.ReadData -> ReadDataInner -> TReader.ReadProperty,重復(fù) ReadRootComponent 的過程。
TReader.ReadComponent 和 TComponent.ReadState 形成遞歸調(diào)用過程,把表單上嵌套的元件創(chuàng)建出來。
最后 InitInheritedComponent 函數(shù)返回,一個 root class 對象從資源中實例化的過程完成。
===============================================================================
⊙ TStream Class 和 TStream.ReadComponent 方法
===============================================================================
TStream 在對象持續(xù)機制扮演的角色是提供一種存儲媒介,由 TFiler 對象使用。TStream 是一個虛類,它定義了數(shù)據(jù)的“流式”讀寫方法。它的繼承類 TFileStream、TMemoryStream、TResourceStream 等實現(xiàn)對不同媒體的讀寫。對象的 persistent 信息可以存儲在任何 TStream 類中,也可以從任何 TStream 中獲得。由于 Delphi 缺省的對象信息存儲在應(yīng)用程序的資源段中,因此,可以從程序的資源段中讀取數(shù)據(jù)的 TResourceStream 類就顯得更加重要。
TStream 定義兩個讀寫緩沖的方法:ReadBuffer 和 WriteBuffer。這兩個方法封裝了 TStream.Read 和 TStream.Write 純虛方法(必須被后繼類重載)。
{ TStream }
procedure ReadBuffer(var Buffer; Count: Longint);
procedure WriteBuffer(const Buffer; Count: Longint);
可以看到這兩個方法的 Buffer 參數(shù)都是無類型的,也就是使用引用的方式傳入的,所以不管是使用單個字符或自定義的結(jié)構(gòu)都是正確的(當(dāng)然,不能使用常量)。Count 指示要讀或?qū)懭氲?Buffer 的大小(Bytes)。
TStream 還定義了兩個元件信息的讀寫方法:ReadComponent 和 WriteComponent。由于 WriteComponent 通常是由 Delphi 的 IDE/編譯器調(diào)用的,很難跟蹤它的執(zhí)行過程,所以我們以后主要考察 ReadComponent 方法。我們可以很容易想像這兩個方法互為逆過程,理解了其中一個也就能知道另一個所做的工作。
{ TStream }
function ReadComponent(Instance: TComponent): TComponent;
procedure WriteComponent(Instance: TComponent);
TStream.ReadComponent 創(chuàng)建了一個 TReader 對象,將自己的對象地址作為參數(shù)傳遞給 Reader,并調(diào)用 Reader.ReadRootComponent 創(chuàng)建對象實例。
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 把自己的對象句柄交給 TReader 之后,就成了 TReader 讀取對象屬性資料的來源。此后 TStream 對象只由 TReader 來掌控,自己不再主動進行其它工作。
===============================================================================
⊙ TReader Class 和 TReader.ReadRootComponent 方法
===============================================================================
TReader 和 TWriter 都是從 TFiler 繼承下來的類。TFiler 是個純虛類,它的構(gòu)造函數(shù)被 TReader 和 TWrite 共享。TFiler.Create 先把 Stream 參數(shù)保存在 FStream 字段中,然后生成一個自己的緩沖區(qū):
constructor TFiler.Create(Stream: TStream; BufSize: Integer);
begin
FStream := Stream; // 保存 stream 對象
GetMem(FBuffer, BufSize); // 創(chuàng)建自己的緩沖區(qū),加速數(shù)據(jù)訪問
FBufSize := BufSize; // 設(shè)置緩沖區(qū)大小
end;
上面說到 TStream.ReadComponent 在創(chuàng)建 TReader 對象之后,立即調(diào)用 TReader.ReadRootComponent 方法。TReader.ReadRootComponent 方法的功能是從 stream 中讀取 root class 對象的屬性。并返回該對象的指針。
{ 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)容是錯誤的。'TPF0' 就是 root class 對象的標(biāo)記。
然后 ReadRootComponent 調(diào)用 ReadPrefix 讀取元件的繼承信息。
如果 Root 參數(shù)是 nil,也就是說 Root 對象還沒被創(chuàng)建,則直接從流中讀取 Root 的類名,再使用 FindClass 函數(shù)找到該類在內(nèi)存中的地址,并調(diào)用該類的構(gòu)造函數(shù)創(chuàng)建 Root 的實例。如果 Root 實例已存在,則調(diào)用內(nèi)嵌的 FindUniquName 函數(shù)檢查 Root.Name 是否與已有的實例重復(fù),如有重復(fù)則在 Root.Name 后加上序號使其唯一。
接下來 ReadRootComponent 調(diào)用 Root 的 ReadState 虛方法從流中讀取 Root 對象的屬性。
===============================================================================
⊙ 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 :表示元件是最上級(top-level)的元件,比如表單或數(shù)據(jù)模塊
如果元件的狀態(tài)中包含 ffChildPos,ReadPrefix 還會讀取元件的創(chuàng)建次序值,存放在 AChildPos 參數(shù)中。
===============================================================================
⊙ TComponent.ReadState 虛方法
===============================================================================
設(shè)置 ReadState 方法的主要目的是在讀取屬性信息的前后可以讓元件進行一些處理工作。ReadState 是 Component Writer 需要注意的方法。
{ TComponent }
procedure ReadState(Reader: TReader); virtual;
由于 ReadState 是虛函數(shù),在 TControl、TWinControl、TCustomForm 等后續(xù)類中都被重載,進行自己需要的操作(比如 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 基本上是個包裝函數(shù),它調(diào)用 TReader.ReadDataInner 讀取 root 對象及 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 從流中讀取對象的屬性,直到遇到 EndOfList 標(biāo)志(vaNull)。再循環(huán)調(diào)用 ReadComponent(nil) 讀取子元件的信息。這兩個方法都是 TReader 的重要方法,后面分兩節(jié)討論。ReadDataInner 在ReadProperty 調(diào)用之后還設(shè)置了元件的 Parent 和 Owner 關(guān)系。
===============================================================================
⊙ TReader.ReadProperty 方法
===============================================================================
ReadProperty 使用 RTTI 函數(shù)將從流中讀出的數(shù)據(jù)設(shè)置為對象的屬性。它先解析從流中讀出的屬性名稱,然后判斷該屬性是否有 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)用自定義存儲過程
if FPropName <> '' then PropertyError(FPropName); // 注意這里
end;
...
end;
ReadPropValue 方法基本上是使用 SetOrdProp、SetFloatProp、SetStrProp、GetEnumValue 等 RTTI 函數(shù)設(shè)置元件的屬性值,它的代碼冗長而簡單,不再單獨列出。下面介紹比較重要的 DefineProperties 函數(shù)。
===============================================================================
⊙ TPersistent.DefineProperties 虛方法
===============================================================================
DefineProperties 虛方法用于元件設(shè)計者自定義非 published 屬性的存儲和讀取方法。 TPersistent 定義的該方法是個空方法,到 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)完成讀屬性值的工作,否則將會觸發(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ù) Ancestor 判斷是否需要寫屬性至流中。例如:當(dāng)前元件的屬性值與原表單類中的元件屬性值相同的時候,可以不寫入(通常是這樣設(shè)計)。
ReadData、WriteData 參數(shù)是從 Filer 對象中讀寫數(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;
對于二進制格式的屬性值,可以使用 TFiler.DefineBinaryProperty 方法讀寫:
procedure DefineBinaryProperty(const Name: string;
ReadData, WriteData: TStreamProc; HasData: Boolean); override;
TStreamProc = procedure(Stream: TStream) of object;
Stream 參數(shù)是從流中讀出的二進制數(shù)據(jù)或要寫入二進制數(shù)據(jù)的流對象句柄。
注意:自己定義屬性的讀寫方法時要記得調(diào)用 inherited DefineProperties(Filer),否則祖先類的自定義屬性讀寫操作不會進行。TControl 是個例外,因為它已經(jīng)定義了 published Left 和 Top 屬性。
===============================================================================
⊙ TReader.ReadComponent 方法
===============================================================================
ReadComponent 的執(zhí)行過程與 ReadRootComponent 的過程很相似,它根據(jù)流中的信息使用 FindComponentClass 方法找到元件類在內(nèi)存中的地址,然后調(diào)用該元件類的構(gòu)造函數(shù)創(chuàng)建對象,接下來調(diào)用新建對象的 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 方法從流中讀出一個 TValueType 類型的數(shù)據(jù),它主要由其它的方法調(diào)用。
TValueType 中只有 vaList 比較特殊,它表示后面的數(shù)據(jù)是一個屬性值系列,以 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 返回流中下一個數(shù)據(jù)的類型,然后將流指針回退至讀數(shù)據(jù)之前。通常用于檢測流中下一個數(shù)據(jù)的類型。
function TReader.NextValue: TValueType;
begin
Result := ReadValue;
Dec(FBufPos);
end;
CheckValue 方法調(diào)用 ReadValue 檢查下一個數(shù)據(jù)類型是否是指定的類型,如果不是則觸發(fā)異常。
ReadListBegin 方法檢查下一個數(shù)據(jù)是否是 vaList,它調(diào)用 CheckValue 方法。
ReadListEnd 方法檢查下一個數(shù)據(jù)是否是 vaNull,它調(diào)用 CheckValue 方法。
SkipValue 方法使用 ReadValue 獲得下一個數(shù)據(jù)的類型,然后將流指針跳過這個數(shù)據(jù)。
===============================================================================
⊙ TReader.ReadStr 方法
===============================================================================
ReadStr 方法讀出流中的短字符串,TReader 內(nèi)部使用它讀取屬性名稱等字符串,元件設(shè)計者應(yīng)該使用 ReadString 函數(shù)讀取屬性值。
function ReadStr: string;
===============================================================================
⊙ TReader.ReadInteger / ReadString / ReadBoolean 系列方法
===============================================================================
TReader 有一系列讀取屬性值的函數(shù),可供元件設(shè)計者使用。
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 的讀方法是因為 TReader 的讀數(shù)據(jù)操作很頻繁,它自己建立了一個緩沖區(qū)(4K),只有當(dāng)緩沖區(qū)中的數(shù)據(jù)讀完之后才會調(diào)用 TStream.Read 再讀入下一段數(shù)據(jù),這樣可以極大地加快讀取速度。Read 是個匯編函數(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ù)用于把對象屬性信息轉(zhuǎn)換為文本形式或二進制形式。
procedure ObjectBinaryToText(Input, Output: TStream);
procedure ObjectTextToBinary(Input, Output: TStream);
新建一個項目,在表單上放置一個 TMemo 控件,然后執(zhí)行以下代碼,就能明白這兩個函數(shù)的作用了。在 Delphi 的 IDE 中,將 DFM 文件進行二進制和文本方式的轉(zhuǎn)換應(yīng)該是通過這兩個函數(shù)進行的。
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;
上面的兩個函數(shù)還有一對增強版本,它們增加了對資源文件格式的轉(zhuǎn)換,實際上也是調(diào)用了上面的函數(shù):
procedure ObjectResourceToText(Input, Output: TStream);
procedure ObjectTextToResource(Input, Output: TStream);
Delphi 編譯程序生成應(yīng)用程序的資源數(shù)據(jù)段,應(yīng)該是用 ObjectTextToResource 函數(shù)進行的。
注:ObjectTextToBinary 調(diào)用了 TParser 對象進行字符串解析工作。
===============================================================================
⊙ 結(jié)束
===============================================================================