Delphi 的RTTI机制-3

====================================
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 属性。

===============================================================================
TReader.ReadComponent 方法
===============================================================================
ReadComponent
的执行过程与 ReadRootComponent 的过程很相似,它根据流中的信息使用FindComponentClass 方法找到元件类在内存中的地址,然后调用该元件类的构造函数创建对象,接下来调用新建对象的 ReadState -> TReader.ReadData -> ReadDataInner -> TReader.ReadProperty,重复 ReadRootComponent 的过程。

  { TReader }
  function ReadComponent(Component: TComponent): TComponent;

TReader.ReadComponent
TComponent.ReadState 形成递归调用过程,把表单上嵌套的元件创建出来。

===============================================================================
TReader.ReadValue / TReader.NextValue 系列方法
===============================================================================
ReadValue
方法从流中读出一个TValueType 类型的数据,它主要由其它的方法调用。

TValueType
中只有 vaList 比较特殊,它表示后面的数据是一个属性值系列,以 vaNull 结束。其余的枚举值的都是指属性的数据类型或值。

  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
方法调用 ReadValue 返回流中下一个数据的类型,然后将流指针回退至读数据之前。通常用于检测流中下一个数据的类型。

  function TReader.NextValue: TValueType;
  begin
    Result := ReadValue;
    Dec(FBufPos);
  end;

CheckValue
方法调用 ReadValue 检查下一个数据类型是否是指定的类型,如果不是则触发异常。

ReadListBegin
方法检查下一个数据是否是 vaList,它调用 CheckValue 方法。

ReadListEnd
方法检查下一个数据是否是 vaNull,它调用 CheckValue 方法。

SkipValue
方法使用 ReadValue 获得下一个数据的类型,然后将流指针跳过这个数据。

===============================================================================
TReader.ReadStr 方法
===============================================================================
ReadStr
方法读出流中的短字符串,TReader 内部使用它读取属性名称等字符串,元件设计者应该使用 ReadString 函数读取属性值。

  function ReadStr: string;

===============================================================================
TReader.ReadInteger / ReadString / ReadBoolean 系列方法
===============================================================================
TReader
有一系列读取属性值的函数,可供元件设计者使用。

    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
中所有的数据都是通过TReader.Read 方法读取的。TReader 不直接调用 TStream 的读方法是因为 TReader 的读数据操作很频繁,它自己建立了一个缓冲区(4K),只有当缓冲区中的数据读完之后才会调用 TStream.Read 再读入下一段数据,这样可以极大地加快读取速度。Read 是个汇编函数,编写得很巧妙,它的代码及注释如下:

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 的缓冲数据是否用尽 }
@@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:
检查要读出的数量是否超过缓冲区大小,如是则分批读取 }
@@2:    CMP     ECX,EBX                    ; if FBufEnd < Count jmp @@3
        JB      @@3
        MOV     ECX,EBX                    ; else ECX <- Count
      { @@3:
分批读取缓冲区 }
@@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:
检查是否读完数据,然后重复 @@1 或退出 }
@@6:    OR      EBX,EBX                    ; if Count = 0 then Exit
        JNE     @@1                        ; Repeat ReadBuffer
        POP     EBX
        POP     EDI
        POP     ESI
end;

===============================================================================
ObjectBinaryToText / ObjectTextToBinary 函数
===============================================================================
Classes.pas
中的 ObjectBinaryToText ObjectTextToBinary 函数用于把对象属性信息转换为文本形式或二进制形式。

  procedure ObjectBinaryToText(Input, Output: TStream);
  procedure ObjectTextToBinary(Input, Output: TStream);

新建一个项目,在表单上放置一个 TMemo 控件,然后执行以下代码,就能明白这两个函数的作用了。在Delphi IDE 中,将 DFM 文件进行二进制和文本方式的转换应该是通过这两个函数进行的。

  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;

上面的两个函数还有一对增强版本,它们增加了对资源文件格式的转换,实际上也是调用了上面的函数:

  procedure ObjectResourceToText(Input, Output: TStream);
  procedure ObjectTextToResource(Input, Output: TStream);

Delphi
编译程序生成应用程序的资源数据段,应该是用ObjectTextToResource 函数进行的。

注:ObjectTextToBinary 调用了 TParser 对象进行字符串解析工作。

posted @ 2014-06-16 10:59  Wishmeluck  阅读(227)  评论(0编辑  收藏  举报