7

I have the following structures in Delphi 2009:

type
  IndiReportIndi = record
    IndiName: string;
    NameNum: integer;
    ReportIndiName: string;
  end;

var
  XRefList: array of IndiReportIndi;

where XRefList is a dynamic array.

I want to save XRefList to a FileStream. How do I do that AND include all the IndiName and ReportIndiName strings so that they will all be retrievable again when I later load from that FileStream?

Michael Petrotta
  • 59,888
  • 27
  • 145
  • 179
lkessler
  • 19,819
  • 36
  • 132
  • 203
  • 1
    Since December 2010, there is a new [OpenSource unit](http://blog.synopse.info/post/2011/03/12/TDynArray-and-Record-compare/load/save-using-fast-RTTI) worth considering for serializing records or dynamic arrays (with a lot more features than serialization) - working for Delphi 5 up to XE2. – Arnaud Bouchez Nov 08 '11 at 07:31
  • @ArnaudBouchez the link is dead. – SHIN JaeGuk Apr 30 '21 at 05:42
  • Proper link is now https://blog.synopse.info/?post/2011/03/12/TDynArray-and-Record-compare/load/save-using-fast-RTTI - working with latest Delphi, but also with FPC/Lazarus. – Arnaud Bouchez May 06 '21 at 15:39

5 Answers5

10
type
  IndiReportIndi = record
    IndiName: string;
    NameNum: integer;
    ReportIndiName: string;
    procedure SaveToStream(Stream: TStream);
    procedure LoadFromStream(Stream: TStream);
  end;

type
  TXRefList = array of IndiReportIndi;

function LoadString(Stream: TStream): string;
var
  N: Integer;

begin
  Result:= '';
  Stream.ReadBuffer(N, SizeOf(Integer));
  if N > 0 then begin
    SetLength(Result, N);
//    Stream.ReadBuffer(Result[1], N * SizeOf(Char));
// fast version - see comment by A.Bouchez
    Stream.ReadBuffer(Pointer(Result)^, N * SizeOf(Char));
  end;
end;

procedure SaveString(Stream: TStream; const S: string);
var
  N: Integer;

begin
  N:= Length(S);
  Stream.WriteBuffer(N, SizeOf(Integer));
  if N > 0 then
//    Stream.WriteBuffer(S[1], N * SizeOf(Char));
// fast version - see comment by A.Bouchez
    Stream.WriteBuffer(Pointer(S)^, N * SizeOf(Char));
end;

procedure IndiReportIndi.LoadFromStream(Stream: TStream);
var
  S: string;

begin
  IndiName:= LoadString(Stream);
  Stream.ReadBuffer(NameNum, SizeOf(Integer));
  ReportIndiName:= LoadString(Stream);
end;

procedure IndiReportIndi.SaveToStream(Stream: TStream);
begin
  SaveString(Stream, IndiName);
  Stream.WriteBuffer(NameNum, SizeOf(Integer));
  SaveString(Stream, ReportIndiName);
end;

function LoadXRefList(Stream: TStream): TXRefList;
var
  N: Integer;
  I: Integer;

begin
  Stream.ReadBuffer(N, SizeOf(Integer));
  if N <= 0 then Result:= nil
  else begin
    SetLength(Result, N);
    for I:= 0 to N - 1 do
      Result[I].LoadFromStream(Stream);
  end;
end;

procedure SaveXRefList(Stream: TStream; const List: TXRefList);
var
  N: Integer;
  I: Integer;

begin
  N:= Length(List);
  Stream.WriteBuffer(N, SizeOf(Integer));
  for I:= 0 to N - 1 do
    List[I].SaveToStream(Stream);
end;
kludg
  • 27,213
  • 5
  • 67
  • 118
  • 2
    You could have used pointer(Result)^ instead of Result[1] in LoadString and pointer(S)^ instead of S[1] in SaveString, because it will save a call to UniqueString(). LoadString could be also a bit faster if coded with result := '' in all cases, then SetLength(Result,N) if N>0: so there will be no memory reallocation (with a slow move), but a string finalize then new allocation (which is faster). – Arnaud Bouchez Dec 07 '10 at 07:39
  • Use a TReader/TWriter to simplify writing of standard data types. Instead of Stream.WriteBuffer(N, SizeOf(Integer)); you would simply write Write.WriteInger(N) and N := Reader.ReadInteger;. They also have support for strings and variants, and code is much more readable. You just duplicated code Delphi alread has. –  Dec 07 '10 at 10:07
  • @ldsandon: TReader/TWriter implementation depends on Delphi version and may change. It is undesirable to rely on TReader/TWriter methods to implement your own data serialization. – kludg Dec 07 '10 at 10:27
  • 1
    The OP record structure will change as well - and your code too depends on the version of Delphi. If the file should be used across application compiled with a different version of Delphi, an header containing version information should be added to allow the reading application to use the correct read functions. –  Dec 07 '10 at 13:55
6
var
  S: TStream;
  W: TWriter;
  A: array of IndiReportIndi;
  E: IndiReportIndi;
  ...
begin
  S := nil;
  W := nil;
  try 
    S := TFileStream.Create(...);
    W := TWriter.Create(S);
    W.WriteInteger(Length(A));
    for E in A do
    begin
      W.WriteString(E.IndiName);
      W.WriteInteger(E.NameNum);
      W.WriteString(E.ReportIndiName);
    end;
  finally
    W.Free;
    S.Free
  end;
end;  

To read those data you use a TReader and ReadInteger/ReadString.

6

Use: http://code.google.com/p/kblib/

type
  IndiReportIndi = record
    IndiName: string;
    NameNum: integer;
    ReportIndiName: string;
  end;

  TXRefList = array of IndiReportIndi;

var
  XRefList: TXRefList;

To save whole XRefList to stream use:

TKBDynamic.WriteTo(lStream, XRefList, TypeInfo(TXRefList));

To load it back:

TKBDynamic.ReadFrom(lStream, XRefList, TypeInfo(TXRefList));
Krystian Bigaj
  • 1,295
  • 8
  • 14
  • Looks like a really nice routine. Thanks for pointing it out. – lkessler Dec 08 '10 at 05:43
  • Nice library. [BUT] if you do it this way, then, if you change your record (let's say you add another field to your record), you cannot load anymore the old files that you saved with the original record. I would use it ONLY if it is guaranteed 101% that your record will never change! But this kind of warranties, are seldom possible in real life. – Gabriel Mar 24 '22 at 11:51
1

Simple TFileStreamEx to save variables

http://forum.chertenok.ru/viewtopic.php?t=4642

Work with them from 2005th year, without any problems...

TuXAPuK
  • 11
  • 2
-1

Just for the answer to be accurate:

Consider taking a look at the TDynArray wrapper, which is able to serialize any record into binary, and also to/from dynamic arrays.

There are a lot of TList-like methods, including new methods (like hashing or binary search).

Very optimized for speed and disk use, works for Delphi 5 up to XE2 - and Open Source.

See also How to store dynamic arrays in a TList?

Arnaud Bouchez
  • 42,305
  • 3
  • 71
  • 159