代码之家  ›  专栏  ›  技术社区  ›  Abstract type

带有TReader/TWriter帮助程序类的ObjectBinaryToText错误

  •  2
  • Abstract type  · 技术社区  · 10 年前

    我创建了两个简单的助手 TWriter.WriteProperties() TReader.ReadProperty() 公共,基于 this example .

    当以二进制对象格式保存持久性内容时,它工作得很好,但在转换为文本时失败。

    关于如何使其工作(文本格式)有什么想法吗?我不想为此重写转换例程。

    这个简单的控制台程序说明了问题:

    program tfiler_persistent_hack;
    {$MODE DELPHI}
    
    uses
      classes, sysutils;
    
    type
    
      TReaderEx = class helper for TReader
        procedure ReadPersistent(aValue: TPersistent);
      end;
    
      TWriterEx = class helper for TWriter
        procedure WritePersistent(aValue: TPersistent);
      end;
    
      TTest = class(TComponent)
      private
        fList: TStringList;
        procedure ListFromReader(aReader: TReader);
        procedure ListToWriter(aWriter: TWriter);
      protected
        procedure defineProperties(aFiler: TFiler); override;
      public
        constructor create(aOwner: TComponent); override;
        destructor destroy; override;
        property list: TStringList read fList;
      end;
    
      procedure TReaderEx.ReadPersistent(aValue: TPersistent);
      begin
        ReadListBegin;
        while not EndOfList do ReadProperty(aValue);
        ReadListEnd;
      end;
    
      procedure TWriterEx.WritePersistent(aValue: TPersistent);
      begin
        WriteListBegin;
        WriteProperties(aValue);
        WriteListEnd;
      end;
    
      procedure TTest.ListFromReader(aReader: TReader);
      begin
        aReader.ReadPersistent(fList);
      end;
    
      procedure TTest.ListToWriter(aWriter: TWriter);
      begin
        aWriter.WritePersistent(fList);
      end;
    
      procedure TTest.defineProperties(aFiler: TFiler);
      begin
        aFiler.defineProperty('the_list_id_liketosave_without_publising', ListFromReader, ListToWriter, true);
      end;
    
      constructor TTest.create(aOwner: TComponent);
      begin
        inherited;
        fList := TStringList.Create;
      end;
    
      destructor TTest.destroy;
      begin
        fList.Free;
        inherited;
      end;
    
    var
      test: TTest;
      str1, str2: TMemoryStream;
    
    const
      itm1 = 'aqwzsx';
      itm2 = 'edcrfv';
    
    begin
      test := TTest.create(nil);
      str1 := TMemoryStream.Create;
      str2 := TMemoryStream.Create;
      try
    
        // bin format passes
        test.list.add(itm1);
        test.list.add(itm2);
        str1.WriteComponent(test);
        str1.SaveToFile('bin.txt');
        str1.Clear;
        test.list.clear;
        str1.LoadFromFile('bin.txt');
        str1.ReadComponent(test);
        assert( test.list.strings[0] = itm1);
        assert( test.list.strings[1] = itm2);
        writeln('bin: zero killed');
    
        // text format does not
        str1.Clear;
        test.list.clear;
        test.list.add(itm1);
        test.list.add(itm2);
        str1.WriteComponent(test);
        str1.Position := 0;
        try
          ObjectBinaryToText(str1, str2);
        except
          writeln('ouch, it hurts (1)');
          exit;
        end;
        str2.SaveToFile('text.txt');
        str1.Clear;
        str2.Clear;
        test.list.clear;
        str1.LoadFromFile('text.txt');
        try
          ObjectTextToBinary(str1, str2);
        except
          writeln('ouch, it hurts (2)');
          exit;
        end;
        str2.Position := 0;
        str2.ReadComponent(test);
        assert( test.list.strings[0] = itm1);
        assert( test.list.strings[1] = itm2);
        writeln('text: zero killed');
    
      finally
        sysutils.DeleteFile('bin.txt');
        sysutils.DeleteFile('text.txt');
        test.Free;
        str1.Free;
        str2.Free;
        readln;
      end;
    
    end.
    

    当我运行它时,我得到以下输出:

    bin:零终止
    哎哟,好痛

    1 回复  |  直到 10 年前
        1
  •  4
  •   Remy Lebeau Ted Lyngmo    10 年前

    如果您使列表属性 published 并删除对的调用 TFiler.DefineProperty() ,一切正常,如预期:

    TTest = class(TComponent)
    private
      fList: TStringList;
      procedure SetList(Value: TStringList);
    public
      constructor Create(aOwner: TComponent); override;
      destructor Destroy; override;
      property list: TStringList read fList;
    published
      property the_list_id_liketosave_without_publising: TStringList read fList write SetList;
    end;
    

    以下是其DFM二进制数据的外观:

    54 50 46 30 05 54 54 65 73 74 00 30 74 68 65 5F : TPF0.TTest.0the_
    6C 69 73 74 5F 69 64 5F 6C 69 6B 65 74 6F 73 61 : list_id_liketosa
    76 65 5F 77 69 74 68 6F 75 74 5F 70 75 62 6C 69 : ve_without_publi
    73 69 6E 67 2E 53 74 72 69 6E 67 73 01 06 06 61 : sing.Strings...a
    71 77 7A 73 78 06 06 65 64 63 72 66 76 00 00 00 : qwzsx..edcrfv...
    

    下面是文本输出:

    object TTest
      the_list_id_liketosave_without_publising.Strings = (
        'aqwzsx'
        'edcrfv')
    end
    

    如您所见,属性名称只有一个字符串:

    the_list_id_liketosave_without_publising.Strings
    

    在内部, TStream.ReadComponent() 读取该字符串并在 . 字符,使用RTTI解析 the_list_id_liketosave_without_publising 根据实际情况 TStringList 对象,然后调用 DefineProperties('Strings') 在该对象上,让它流式传输其字符串列表数据,一切都很好。

    ObjectBinaryToText() 做不了那么多工作。事实上,在查看RTL源代码后,发现 对象BinaryToText() (至少在Delphi,但我确信FreePascal也是如此) 没有 支持自定义流媒体 TComponent.DefineProperties() 根本不需要 DefineProperties() )!. 这是你问题的根源。 对象BinaryToText() 未实现完全流式传输系统 ReadComponent() 实现,只是它的一个子集。

    然而,在这种情况下,一切都很好,因为 T字符串列表 以易于使用的简单格式写入自定义流数据 对象BinaryToText() 以进行处理。

    什么时候 对象BinaryToText() 读取属性名称字符串,它按原样将其写入,而不以任何方式解析它,然后读取下一个字节并相应地处理它。 T字符串列表 使用以下格式:

    vaList (TWriter.WriteListBegin())
    vaString for each string (TWriter.WriteString())
    vaNull (TWriter.WriteListEnd())
    

    对象BinaryToText() 识别这些标记,因此当它遇到 vaList (十六进制 01 )它需要在循环中读取值,直到读取 vaNull (十六进制 00 ),它知道如何阅读 vaString (十六进制 06 )值。因此,写下 Strings 数据转换为输出文本。

    在您的情况下 TTest 自定义流,它创建的DFM二进制数据有点不同:

    54 50 46 30 05 54 54 65 73 74 00 28 74 68 65 5F : TPF0.TTest.(the_
    6C 69 73 74 5F 69 64 5F 6C 69 6B 65 74 6F 73 61 : list_id_liketosa
    76 65 5F 77 69 74 68 6F 75 74 5F 70 75 62 6C 69 : ve_without_publi
    73 69 6E 67 01 07 53 74 72 69 6E 67 73 01 06 06 : sing..Strings...
    61 71 77 7A 73 78 06 06 65 64 63 72 66 76 00 00 : aqwzsx..edcrfv..
    00 00                                           : ..
    

    如您所见,存在两个单独的属性名称字符串:

    the_list_id_liketosave_without_publising
    

    Strings
    

    什么时候 对象BinaryToText() 读取 列表_喜欢保存而不发布 字符串,它假定它是完整的属性名,并读取下一个字节以确定要读取的属性的数据类型。该字节(十六进制 01 )被解释为 vaList(虚拟列表) .下一个字节(十六进制 07 )被解释为 vaIdent (又名 not vaNull ),因此它假设正在读取子属性的非空列表(实际上不是)。它试图读取 vaIdent公司 “属性”,其中下一个字节(十六进制 53 )被解释为缩进的字节长度(不是),然后它尝试读取那么多字节(十进制83)并失败。

    为了使您 T测试 自定义流媒体可以正确使用 对象BinaryToText() ,您必须通过复制与 TStrings.DefineProperties() 实现(正如其流式方法 private 无法访问),例如:

    TTest = class(TComponent)
    private
      fList: TStringList;
      procedure ListFromReader(aReader: TReader);
      procedure ListToWriter(aWriter: TWriter);
    protected
      procedure DefineProperties(aFiler: TFiler); override;
    public
      constructor Create(aOwner: TComponent); override;
      destructor Destroy; override;
      property list: TStringList read fList;
    end;
    
    procedure TTest.ListFromReader(aReader: TReader);
    begin
      aReader.ReadListBegin;
      fList.BeginUpdate;
      try
        fList.Clear;
        while not aReader.EndOfList do fList.Add(aReader.ReadString);
      finally
        fList.EndUpdate;
      end;
      aReader.ReadListEnd;
    end;
    
    procedure TTest.ListToWriter(aWriter: TWriter);
    var
      I: Integer;
    begin
      aWriter.WriteListBegin;
      for I := 0 to fList.Count - 1 do aWriter.WriteString(fList[I]);
      aWriter.WriteListEnd;
    end;
    
    procedure TTest.DefineProperties(aFiler: TFiler);
    begin
      inherited;
      aFiler.DefineProperty('the_list_id_liketosave_without_publising', ListFromReader, ListToWriter, fList.Count > 0);
    end;
    
    constructor TTest.Create(aOwner: TComponent);
    begin
      inherited;
      fList := TStringList.Create;
    end;
    
    destructor TTest.Destroy;
    begin
      fList.Free;
      inherited;
    end;
    

    生成此DFM二进制数据的函数:

    54 50 46 30 05 54 54 65 73 74 00 28 74 68 65 5F : TPF0.TTest.(the_
    6C 69 73 74 5F 69 64 5F 6C 69 6B 65 74 6F 73 61 : list_id_liketosa
    76 65 5F 77 69 74 68 6F 75 74 5F 70 75 62 6C 69 : ve_without_publi
    73 69 6E 67 01 06 06 61 71 77 7A 73 78 06 06 65 : sing...aqwzsx..e
    64 63 72 66 76 00 00 00                         : dcrfv...
    

    生成此输出文本的函数:

    object TTest
      the_list_id_liketosave_without_publising = (
        'aqwzsx'
        'edcrfv')
    end
    

    就是这样 对象BinaryToText() 它并不是像您试图实现的那样为通用自定义流媒体而设计的。它对它能(和不能)处理的事情非常专业。请记住,它主要设计用于IDE编辑器向用户显示DFM,因此它依赖于使用简单流格式的已发布组件。您尝试实现的内容超出了它的解析能力。

    两个字节有什么区别?