Search code examples
windowsdelphifilerecordstyped

saving a records containing a member of type string to a file (Delphi, Windows)


I have a record that looks similar to:

type
  TNote = record
    Title : string;
    Note  : string;
    Index : integer;
  end;

Simple. The reason I chose to set the variables as string (as opposed to an array of chars) is that I have no idea how long those strings are going to be. They can be 1 char long, 200 or 2000. Of course when I try to save the record to a type file (file of...) the compiler complains that I have to give a size to string. Is there a way to overcome this? or a way to save those records to an untyped file and still maintain a sort of searchable way?

Please do not point me to possible solutions, if you know the solution please post code. Thank you


Solution

  • You can't do it with a typed file. Try something like this, with a TFileStream:

    type
       TStreamEx = class helper for TStream
       public
          procedure writeString(const data: string);
          function readString: string;
          procedure writeInt(data: integer);
          function readInt: integer;
      end;
    
    function TStreamEx.readString: string;
    var
       len: integer;
       iString: UTF8String;
    begin
       self.readBuffer(len, 4);
       if len > 0 then
       begin
          setLength(iString, len);
          self.ReadBuffer(iString[1], len);
          result := string(iString);
       end;
    end;
    
    procedure TStreamEx.writeString(const data: string);
    var
       len: cardinal;
       oString: UTF8String;
    begin
       oString := UTF8String(data);
       len := length(oString);
       self.WriteBuffer(len, 4);
       if len > 0 then
          self.WriteBuffer(oString[1], len);
    end;
    
    function TStreamEx.readInt: integer;
    begin
       self.readBuffer(result, 4);
    end;
    
    procedure TStreamEx.writeInt(data: integer);
    begin
       self.WriteBuffer(data, 4);
    end;
    
    type
      TNote = record
        Title : string;
        Note  : string;
        Index : integer;
        procedure Save(stream: TStream);
      end;
    
    procedure TNote.Save(stream: TStream);
    var
       temp: TMemoryStream;
    begin
       temp := TMemoryStream.Create;
       try
          temp.writeString(Title);
          temp.writeString(Note);
          temp.writeInt(Index);
          temp.seek(0, soFromBeginning);
          stream.writeInt(temp.size);
          stream.copyFrom(temp, temp.size);
       finally
          temp.Free;
       end;
    end;
    

    I'll leave the Load procedure to you. Same basic idea, but it shouldn't need a temp stream. With the record size in front of each entry, you can read it and know how far to skip if you're looking for a certain record # instead of reading the whole thing.

    EDIT: This was written specifically for versions of Delphi that use Unicode strings. On older versions, you could simplify it quite a bit.