zoukankan      html  css  js  c++  java
  • 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 对象进行字符串解析工作。

  • 相关阅读:
    css实现图像边框的样式
    css3 实现div靠右对齐
    将div水平,垂直居中的方式
    使用vue-cli可视化的方式创建项目后如何关闭ESLint代码检测
    清楚html和css标签自带默认样式
    vue动态请求到的多重数组循环遍历,取值问题,如果某个值存在则显示,不存在则不显示。
    python 编程
    python 错题集
    python+selenium页面自动化 元素定位实际遇到的各种问题(持续更新)
    使用Fiddle抓取IOS手机
  • 原文地址:https://www.cnblogs.com/luckForever/p/7254566.html
Copyright © 2011-2022 走看看