程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> Delphi RTTI 解析

Delphi RTTI 解析

編輯:Delphi

Delphi 的RTTI機制淺探

目 錄
===============================================================================
⊙ DFM 文件與持續機制(persistent)
⊙ ReadComponentResFile / WriteComponentResFile 函數
⊙ Delphi 持續機制框架簡述
⊙ 一個 TForm 對象的創建過程
⊙ 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 函數
===============================================================================


本文排版格式為:
    正文由窗口自動換行;所有代碼以 80 字符為邊界;中英文字符以空格符分隔。

(作者保留對本文的所有權利,未經作者同意請勿在在任何公共媒體轉載。)


正 文
===============================================================================
⊙ DFM 文件與持續機制(persistent)
===============================================================================
我們在使用 Delphi 的 IDE 進行快速開發的時候,可以方便地從元件面板上拖放元件(component)至表單,完成表單的界面和事件設計。Delphi 將這些界面的設計期信息保存在表單相應的 DFM 文件中,方便程序員隨時讀取和修改。

DFM 文件根據元件在表單上的嵌套層次存放元件屬性,以下是一個 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

應用程序編譯之後,DFM 文件的信息被二進制化了,這些二進制信息存儲在應用程序的資源(resource)段中。每個表單(也就是 class)及表單上的元件在資源段中存儲為與表單同名的資源,可以使用 FindResource API 獲得。應用程序在運行期創建表單實例的時候,會從資源段中讀取表單的屬性,還原設計期的設置。這種將類型信息保存在文件中,並且可以在運行期恢復類型的操作,在本文中被稱之為持續(persistent)機制。持續機制是 Delphi 成為 RAD 工具的原因之一。

持續機制和 RTTI 是緊密結合的,但本文不討論 RTTI(關於 RTTI 可參考我前幾天寫的兩篇筆記),只討論實現持續機制的總體框架及相關類(class)。這些類包括 TStream、TFiler、TReader、TWriter、TParser、TPersisetent、TComponent、TCustomForm 等。

===============================================================================
⊙ ReadComponentResFile / WriteComponentResFile 函數
===============================================================================
讓我們從一個比較直觀的例子開始。

Classes.pas 中定義了兩個函數 ReadComponentResFile 和 WriteComponentResFile,它們的功能是“把元件的屬性信息保存到文件”和“從文件中恢復元件屬性信息”。

先做個試驗。新建一個項目,在 Form1 上放置兩個 Button 和一個 Memo。Button 的 Click 事件代碼如下。按 F9 運行該項目,先在 Memo1 中輸入一些字符,然後按下 Button1,再按下 Button2,你會看一個新建的 Form。它的屬性幾乎和 Form1 一樣,甚至連 Memo1 中的字符都保存下來了,唯一的不同只是它的 Name 屬性變成了“Form1_1”。你可以查看 FORM1.RES 文件的內容看看 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 函數的代碼如下,它只是調用 Stream 對象的 WriteComponentRes 方法將對象屬性保存到資源文件中的:

  procedure WriteComponentResFile(const FileName: string; Instance: TComponent);
  begin
    Stream := TFileStream.Create(FileName, fmCreate);
    Stream.WriteComponentRes(Instance.ClassName, Instance);
    Stream.Free;
  end;

ReadComponentResFile 函數也是調用 Stream 的方法實現從文件中讀取對屬信息:

  function ReadComponentResFile(const FileName: string; Instance: TComponent):
    TComponent;
  begin
    Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
    Result := Stream.ReadComponentRes(Instance);
    Stream.Free;
  end;

ReadComponentResFile 函數可以通過 Instance 參數傳入對象句柄,也可以通過返回值獲得對象句柄。Instance 參數只能是已實例化的對象或 nil。如果是 nil,那麼 ReadComponentResFile 會自動根據文件信息創建對象實例,但必須使用 RegisterClass 函數注冊將要被載入的類,否則會觸發異常。

有個類似的函數 ReadComponentRes,它從應用程序的資源段中恢復對象的屬性信息。它的 ResName 參數就是表單類的名稱:

  function ReadComponentRes(const ResName: string; Instance: TComponent):
    TComponent;

===============================================================================
⊙ Delphi 持續機制框架簡述
===============================================================================
持續機制的實現必須由 IDE、編譯器、表單類、元件類和輔助類合作完成。

這裡的表單類不是指一般所指的 TForm class,在 Delphi 的幫助文件中,稱之為“root class”。root class 是指能在設計期被 Form Designer 作為最上層編輯表單的類(如 TCustomForm、TFrame、TDataModule 等)。Delphi 在設計期將元件的 published 屬性的值保存在 .DFM 文件中,也只有 published 的屬性才能被 Object Insepector 設置賦值。

Form Designer 設計的 root class 對象在編譯時,Delphi 將對象的屬性以及其所包含的元件的屬性保存在應用程序的資源段(RT_RCDATA)中。

輔助類包括 TStream、TReader、TWriter、TParser 等。這些類起著中間層的作用,用於存儲和讀取對象屬性的信息。雖然我稱它們為輔助類,但是保存和恢復對象信息的實際操作是由它們完成的。

===============================================================================
⊙ 一個 TForm 對象的創建過程
===============================================================================
下面是一個典型的表單 Form1 的創建過程,縮進代表調用關系(Form1.ReadState 例外,防止縮進太多),帶“?”的函數表示我尚未仔細考察的部分,帶“*”表示元件編寫者需要注意的函數。

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 函數中先調用 CreateNew 設置缺省的表單屬性,然後調用Classes.InitInheritedComponent 函數。

InitInheritedComponent 用於初始化一個 root class 對象。該函數的功能就是從應用程序的資源中恢復設計期的表單信息。InitInheritedComponent 的聲明如下:

  { Classes.pas }
  function InitInheritedComponent(Instance: TComponent;
    RootAncestor: TClass): Boolean;

InitInheritedComponent 傳入兩個參數:Instance 參數代表將要從資源段中恢復信息的對象,RootAncestor 表示該對象的祖先類。如果從資源中恢復信息成功,則返回 True,否則返回 False。InitInheritedComponent 通常只在 root class 的構造函數中調用。

  constructor TCustomForm.Create(AOwner: TComponent);
  begin
    ...
    CreateNew(AOwner);                              // 初始化缺省的 Form 屬性
    Include(FFormState, fsCreating);                // 標記為 Creating 狀態
    if not InitInheritedComponent(Self, TForm) then // 從資源中恢復 Form 信息
      raise EResNotFound.CreateFmt(SResNotFound, [ClassName]);
    ...
    Exclude(FFormState, fsCreating);                // 取消 Creating 狀態
  end;

InitInheritedComponent 調用自身內置的函數:InitComponent(Instance.ClassType)。InitComponent 先判斷 Instance.ClassType 是否是 TComponent 或 RootAncestor,如果是則返回 False 並退出,否則調用 InternalReadComponentRes。

* InitComponent 遞歸調用自己檢查類信息。沒看懂為什麼要這樣設計,如果有誰看懂了請告訴我。

  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,調用 FindResourceHInstance 找到 class 資源所在模塊的 HInst 句柄(因為 class 可能是在動態鏈接庫中),並通過引用方式傳遞 Instance 對象(* 好像沒有必要使用引用方式,InitInheritedComponent 也沒有使用引用方式):

  { Classes.pas }
  function InternalReadComponentRes(const ResName: string; HInst: THandle;
    var Instance: TComponent): Boolean;

InternalReadComponentRes 先檢查 class 資源是否存在,如果存在則創建一個 TResourceStream 對象(TResourceStream 的 Create 構造函數把 class 信息的資源內存地址和大小記錄在成員字段中),然後使用 TResourceStream.ReadComponent 方法從資源中讀取 Instance 的信息。TResourceStream 並沒有定義 ReadComponent 方法,而是使用祖先類 TStream 的方法。TStream.ReadComponent 創建一個 TReader 對象,然後使用自己的對象地址(Self)作為參數,調用 TReader.ReadRootComponent 讀取 Instance 對象的內容。

  { TReader }
  function ReadRootComponent(Root: TComponent): TComponent;

ReadRootComponent 先調用 TReader.ReadSignature。ReadSignature 從 stream 中讀取 4 字節的內容,如果讀出來的內容不是 'TPF0',則觸發異常(SInvalidImage),表示該 stream 的內容是錯誤的。然後 ReadRootComponent 調用 ReadPrefix 讀取元件的狀態信息。

如果 Root 參數是 nil,也就是說 Root 對象還沒被創建,則直接從流中讀取 Root 的類名,再使用 FindClass 函數找到該類在內存中的地址,並調用該類的構造函數創建 Root 的實例。

接下來 ReadRootComponent 調用 Root 的 ReadState 虛函數從流中讀取 Root 對象的屬性。TComponent.ReadState 只有一行代碼:Reader.ReadData(Self);。

ReadData 調用 ReadDataInner 讀取 root 元件及 root 的子元件的屬性信息。

ReadDataInner 先循環調用 ReadProperty 從流中讀取 root 元件的屬性,直到遇到 EndOfList 標志(vaNull)。ReadProperty 使用 RTTI 函數,將從流中讀出的數據設置為對象的屬性。ReadProperty 中還調用了 Instance.DefineProperties,用於實現自定義的屬性存儲。ReadDataInner 然後循環調用 ReadComponent(nil) 讀取子元件的信息。

ReadComponent 的執行過程與 ReadRootComponent 的過程很相似,它根據流中的信息使用 FindComponentClass 找到元件類在內存中的地址,然後調用該元件類的構造函數創建對象,接下來調用新建對象的 ReadState -> TReader.ReadData -> ReadDataInner -> TReader.ReadProperty,重復 ReadRootComponent 的過程。

TReader.ReadComponent 和 TComponent.ReadState 形成遞歸調用過程,把表單上嵌套的元件創建出來。

最後 InitInheritedComponent 函數返回,一個 root class 對象從資源中實例化的過程完成。

===============================================================================
⊙ TStream Class 和 TStream.ReadComponent 方法
===============================================================================
TStream 在對象持續機制扮演的角色是提供一種存儲媒介,由 TFiler 對象使用。TStream 是一個虛類,它定義了數據的“流式”讀寫方法。它的繼承類 TFileStream、TMemoryStream、TResourceStream 等實現對不同媒體的讀寫。對象的 persistent 信息可以存儲在任何 TStream 類中,也可以從任何 TStream 中獲得。由於 Delphi 缺省的對象信息存儲在應用程序的資源段中,因此,可以從程序的資源段中讀取數據的 TResourceStream 類就顯得更加重要。

TStream 定義兩個讀寫緩沖的方法:ReadBuffer 和 WriteBuffer。這兩個方法封裝了 TStream.Read 和 TStream.Write 純虛方法(必須被後繼類重載)。

  { TStream }
  procedure ReadBuffer(var Buffer; Count: Longint);
  procedure WriteBuffer(const Buffer; Count: Longint);

可以看到這兩個方法的 Buffer 參數都是無類型的,也就是使用引用的方式傳入的,所以不管是使用單個字符或自定義的結構都是正確的(當然,不能使用常量)。Count 指示要讀或寫入的 Buffer 的大小(Bytes)。

TStream 還定義了兩個元件信息的讀寫方法:ReadComponent 和 WriteComponent。由於 WriteComponent 通常是由 Delphi 的 IDE/編譯器調用的,很難跟蹤它的執行過程,所以我們以後主要考察 ReadComponent 方法。我們可以很容易想像這兩個方法互為逆過程,理解了其中一個也就能知道另一個所做的工作。

  { TStream }
  function ReadComponent(Instance: TComponent): TComponent;
  procedure WriteComponent(Instance: TComponent);

TStream.ReadComponent 創建了一個 TReader 對象,將自己的對象地址作為參數傳遞給 Reader,並調用 Reader.ReadRootComponent 創建對象實例。

  function TStream.ReadComponent(Instance: TComponent): TComponent;
  var
    Reader: TReader;
  begin
    Reader := TReader.Create(Self, 4096);        // 4096 是緩沖區大小
    Result := Reader.ReadRootComponent(Instance);
    Reader.Free;
  end;

TStream 把自己的對象句柄交給 TReader 之後,就成了 TReader 讀取對象屬性資料的來源。此後 TStream 對象只由 TReader 來掌控,自己不再主動進行其它工作。

===============================================================================
⊙ TReader Class 和 TReader.ReadRootComponent 方法
===============================================================================
TReader 和 TWriter 都是從 TFiler 繼承下來的類。TFiler 是個純虛類,它的構造函數被 TReader 和 TWrite 共享。TFiler.Create 先把 Stream 參數保存在 FStream 字段中,然後生成一個自己的緩沖區:

  constructor TFiler.Create(Stream: TStream; BufSize: Integer);
  begin
    FStream := Stream;          // 保存 stream 對象
    GetMem(FBuffer, BufSize);   // 創建自己的緩沖區,加速數據訪問
    FBufSize := BufSize;        // 設置緩沖區大小
  end;

上面說到 TStream.ReadComponent 在創建 TReader 對象之後,立即調用 TReader.ReadRootComponent 方法。TReader.ReadRootComponent 方法的功能是從 stream 中讀取 root class 對象的屬性。並返回該對象的指針。

  { TReader }
  function ReadRootComponent(Root: TComponent): TComponent;

ReadRootComponent 先調用 TReader.ReadSignature。

TReader.ReadSignature 方法從 stream 中讀取 4 字節的內容,如果讀出來的內容不是 'TPF0',則觸發異常(SInvalidImage),表示該 stream 的內容是錯誤的。'TPF0' 就是 root class 對象的標記。

然後 ReadRootComponent 調用 ReadPrefix 讀取元件的繼承信息。

如果 Root 參數是 nil,也就是說 Root 對象還沒被創建,則直接從流中讀取 Root 的類名,再使用 FindClass 函數找到該類在內存中的地址,並調用該類的構造函數創建 Root 的實例。如果 Root 實例已存在,則調用內嵌的 FindUniquName 函數檢查 Root.Name 是否與已有的實例重復,如有重復則在 Root.Name 後加上序號使其唯一。

接下來 ReadRootComponent 調用 Root 的 ReadState 虛方法從流中讀取 Root 對象的屬性。

===============================================================================
⊙ TReader.ReadPrefix 方法
===============================================================================
ReadPrefix 方法用於讀取元件的狀態信息,這些信息是由 Writer 在寫入元件屬性之前寫入的。

  { TReader }
  procedure ReadPrefix(var Flags: TFilerFlags; var AChildPos: Integer); virtual;

Flags 參數是以引用方式傳遞的,用於設置元件的在表單中的狀態,元件的狀態在這裡包含三種情況:

  ffInherited:表示元件存在於表單的父類之中
  ffChildPos :表示元件在表單中的創建次序(creation order)是重要的
  ffInline   :表示元件是最上級(top-level)的元件,比如表單或數據模塊

如果元件的狀態中包含 ffChildPos,ReadPrefix 還會讀取元件的創建次序值,存放在 AChildPos 參數中。

===============================================================================
⊙ TComponent.ReadState 虛方法
===============================================================================
設置 ReadState 方法的主要目的是在讀取屬性信息的前後可以讓元件進行一些處理工作。ReadState 是 Component Writer 需要注意的方法。

  { TComponent }
  procedure ReadState(Reader: TReader); virtual;

由於 ReadState 是虛函數,在 TControl、TWinControl、TCustomForm 等後續類中都被重載,進行自己需要的操作(比如 DisableAlign、UpdateControlState)。

TComponent.ReadState 只有一行代碼:Reader.ReadData(Self);

注意:自己重載 ReadState 方法必須調用 inherited 。

===============================================================================
⊙ TReader.ReadData 方法
===============================================================================
上面說到 TComponent.ReadState 又回頭調用 TReader.ReadData 方法。它的主要代碼如下:

  { TReader }
  procedure TReader.ReadData(Instance: TComponent);
  begin
    ...
    ReadDataInner(Instance);
    DoFixupReferences;
    ...
  end;

TReader.ReadData 基本上是個包裝函數,它調用 TReader.ReadDataInner 讀取 root 對象及 root 所包含的元件的屬性信息。

===============================================================================
⊙ TReader.ReadDataInner 方法
===============================================================================
ReadDataInner 負責讀取元件的屬性和子元件的屬性,它的主要代碼如下:

  procedure TReader.ReadDataInner(Instance: TComponent);
    begin
    ...
    while not EndOfList do ReadProperty(Instance);
    ...
    while not EndOfList do ReadComponent(nil);
    ...
  end;

ReadDataInner 先循環調用 ReadProperty 從流中讀取對象的屬性,直到遇到 EndOfList 標志(vaNull)。再循環調用 ReadComponent(nil) 讀取子元件的信息。這兩個方法都是 TReader 的重要方法,後面分兩節討論。ReadDataInner 在ReadProperty 調用之後還設置了元件的 Parent 和 Owner 關系。

===============================================================================
⊙ TReader.ReadProperty 方法
===============================================================================
ReadProperty 使用 RTTI 函數將從流中讀出的數據設置為對象的屬性。它先解析從流中讀出的屬性名稱,然後判斷該屬性是否有 RTTI 信息,如果有則調用 TReader.ReadPropValue 方法從流中讀取屬性值;如果該屬性沒有 RTTI 信息,說明該屬性不屬於 published 段,而是由元件自己寫入的,因此調用 TPersistent.DefineProperties 讀取自定義的元件信息。ReadProperty 的關鍵代碼:

  procedure TReader.ReadProperty(AInstance: TPersistent);
  begin
    ...
    PropInfo := GetPropInfo(Instance.ClassInfo, FPropName);
    if PropInfo <> nil then                             // 檢查屬性 RTTI 信息
      ReadPropValue(Instance, PropInfo)                 // 從流中讀取屬性
    else begin
      Instance.DefineProperties(Self);                  // 調用自定義存儲過程
      if FPropName <> '' then PropertyError(FPropName); // 注意這裡
    end;
    ...
  end;

ReadPropValue 方法基本上是使用 SetOrdProp、SetFloatProp、SetStrProp、GetEnumValue 等 RTTI 函數設置元件的屬性值,它的代碼冗長而簡單,不再單獨列出。下面介紹比較重要的 DefineProperties 函數。

===============================================================================
⊙ TPersistent.DefineProperties 虛方法
===============================================================================
DefineProperties 虛方法用於元件設計者自定義非 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 調用 Filer.DefineProperty 或 DefineBinaryProperty 方法讀寫流中屬性值。

TReader.DefineProperty 方法檢查傳入的屬性名稱是否與當前流中讀到的屬性名稱相同,如果相同,則調用傳入的 ReadData 方法讀取數據,並設置 FPropName 為空,用以通知 ReadProperty 已經完成讀屬性值的工作,否則將會觸發異常。

  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 根據 HasData 參數決定是否需要寫屬性值。

  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,表示當前正在讀取的元件繼承自表單父類中的元件,元件設計者可以根據 Ancestor 判斷是否需要寫屬性至流中。例如:當前元件的屬性值與原表單類中的元件屬性值相同的時候,可以不寫入(通常是這樣設計)。

ReadData、WriteData 參數是從 Filer 對象中讀寫數據的方法地址,它們的類型是:

  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 參數是從流中讀出的二進制數據或要寫入二進制數據的流對象句柄。

注意:自己定義屬性的讀寫方法時要記得調用 inherited DefineProperties(Filer),否則祖先類的自定義屬性讀寫操作不會進行。TControl 是個例外,因為它已經定義了 published Left 和 Top 屬性。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved