Recent

Author Topic: Writing contents of an object to a binary file  (Read 3075 times)

mikea105

  • New Member
  • *
  • Posts: 14
Writing contents of an object to a binary file
« on: November 28, 2015, 10:16:37 pm »
I'm no doubt missing something basic here, but I can't figure out how to write (and later, read) data from an object to a binary file.  Maybe I should be doing something completely different?  Anyhow, here's what I'm doing:

var
  ConfigDir : String;
  fsOut : TFileStream;
  ConfigSize : Integer;
begin                                 
...
  [note: ProgConfig is an object that contains integers, strings, and some enumerations]
    ConfigSize := ProgConfig.InstanceSize;
    fsOut := TFileStream.Create( FileName, fmCreate );
    fsOut.Write( ConfigSize, sizeof( Integer ) );
    fsOut.Write( ProgConfig, ConfigSize );
    fsOut.Free;                                             
...
end;

The file it writes does not appear to contain the data it should.  I look at it with a hex editor, and I don't see any of the strings there. 

Here's the code I'm using to read it back in:

var
  ConfigDir : String;
  fsIn : TFileStream;
  ConfigSize : Integer;
begin
  Result := True;
  ConfigDir := GetAppConfigDir( False );
...
      begin
        fsIn := TFileStream.Create( FileName, fmOpenRead );
        fsIn.Read( ConfigSize, sizeof( Integer ) );
        fsIn.Read( ProgConfig, ConfigSize );
        fsIn.Free;
      end;                                                           

...

Clearly I'm doing something very wrong here.  Am I completely on the wrong track?  If so, what should I be doing?  Must I convert everything to a string and save it as a text file?  Thanks in advance for any help!


kapibara

  • Hero Member
  • *****
  • Posts: 610
Re: Writing contents of an object to a binary file
« Reply #1 on: November 29, 2015, 01:43:23 am »
An object is not saved like that. You have to save each value (field) separately. Also, it's very convenient if a class knows how to save and load itself. You could give the class TProgConfig SaveToFile / LoadFromFile methods.

Lets say your class looks like this:

Code: Pascal  [Select][+][-]
  1. TProgConfig = class
  2. public
  3.   FMyString: string;
  4.   procedure SaveToFile(AFileName: string);
  5.   procedure LoadFromFile(AFileName: string);
  6. end;
  7.  

Then you could implement saving/loading like below. (untested) Note that the strings is saved to stream in two steps: first the length, then the content. Thats how you have to do it with strings. When loading, this procedure is reversed.

Code: Pascal  [Select][+][-]
  1. procedure TProgConfig.SaveToFile(AFileName: string);
  2. var
  3.   Len : integer;
  4.   aStream : TFileStream;
  5. begin
  6.   Len := Length(FMyString);
  7.   try
  8.     aStream := TFileStream.Create(AFileName, fmCreate or fmShareExclusive);
  9.     aStream.WriteBuffer( Len, SizeOf(Len) );
  10.     aStream.WriteBuffer( PChar(FMyString)^, Len);
  11.   finally
  12.     aStream.Free;
  13.   end;
  14. end;
  15.  
  16. procedure TProgConfig.LoadFromFile(AFileName: string);
  17. var
  18.   Len: integer;
  19.   aStream: TFileStream;
  20. begin
  21.   try
  22.     Stream:= TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone);
  23.     aStream.ReadBuffer( Len, SizeOf(Len) );
  24.     SetString(FMyString, PChar(nil), Len);
  25.     aStream.ReadBuffer( PChar(FMyString)^, Len );
  26.   finally
  27.     aStream.Free;
  28.   end;
  29. end;
  30.  

If you have more variables to save, add them to the class and add
code for reading and writing.
« Last Edit: November 29, 2015, 01:56:36 am by kapibara »
Lazarus trunk / fpc 3.2.2 / Kubuntu 22.04 - 64 bit

mikea105

  • New Member
  • *
  • Posts: 14
Re: Writing contents of an object to a binary file
« Reply #2 on: November 29, 2015, 01:54:52 am »
Thank you so very much!  Your code makes a great deal more sense than mine.  After I posted this, I found a reference that said you can't write classes with strings like I was doing.  I was wondering  how I should do it.  Thanks to you, I can see a way forward now. 

Again, thanks very much!   :)

guest58172

  • Guest
Re: Writing contents of an object to a binary file
« Reply #3 on: November 29, 2015, 08:32:31 am »
If your Config object is a TComponent then you can use the native Object Pascal component streaming system to save and reload automatically your published properties.

Usually I use these functions:

Code: Pascal  [Select][+][-]
  1.   (**
  2.    * Save a component with a readable aspect.
  3.    *)
  4.   procedure saveCompToTxtFile(const aComp: TComponent; const aFilename: string);
  5.  
  6.   (**
  7.    * Load a component. Works in pair with saveCompToTxtFile().
  8.    *)
  9.   procedure loadCompFromTxtFile(const aComp: TComponent; const aFilename: string;
  10.     aPropNotFoundHandler: TPropertyNotFoundEvent = nil; anErrorHandler: TReaderError = nil);  

and the implementation:

Code: Pascal  [Select][+][-]
  1. procedure saveCompToTxtFile(const aComp: TComponent; const aFilename: string);
  2. var
  3.   str1, str2: TMemoryStream;
  4. begin
  5.   str1 := TMemoryStream.Create;
  6.   str2 := TMemoryStream.Create;
  7.   try
  8.     str1.WriteComponent(aComp);
  9.     str1.Position := 0;
  10.     ObjectBinaryToText(str1,str2);
  11.     ForceDirectories(ExtractFilePath(aFilename));
  12.     str2.SaveToFile(aFilename);
  13.   finally
  14.     str1.Free;
  15.     str2.Free;
  16.   end;
  17. end;
  18.  
  19. procedure loadCompFromTxtFile(const aComp: TComponent; const aFilename: string;
  20.   aPropNotFoundHandler: TPropertyNotFoundEvent = nil; anErrorHandler: TReaderError = nil);
  21. var
  22.   str1, str2: TMemoryStream;
  23.   rdr: TReader;
  24. begin
  25.   str1 := TMemoryStream.Create;
  26.   str2 := TMemoryStream.Create;
  27.   try
  28.     str1.LoadFromFile(aFilename);
  29.     str1.Position := 0;
  30.     ObjectTextToBinary(str1, str2);
  31.     str2.Position := 0;
  32.     try
  33.       rdr := TReader.Create(str2, 4096);
  34.       try
  35.         rdr.OnPropertyNotFound := aPropNotFoundHandler;
  36.         rdr.OnError := anErrorHandler;
  37.         rdr.ReadRootComponent(aComp);
  38.       finally
  39.         rdr.Free;
  40.       end;
  41.     except
  42.     end;
  43.   finally
  44.     str1.Free;
  45.     str2.Free;
  46.   end;
  47. end;

This is advantageous because the property setters are called automatically. But sometimes it sucks, particularly if the class to dump is likely to be modified a lot. I've already posted something about this a few monthes ago, just search the forum if you want to know more.
You can do the same with JSON or XML, there are also some utils to map directly  these formats to published members.

 

TinyPortal © 2005-2018