Paul Curran
Paul Curran

Reputation: 27

Binary file error in Lazarus Pascal with custom records - error SIGSEGV

I don't work with Pascal very often so I apologise if this question is basic. I am working on a binary file program that writes an array of custom made records to a binary file.

Eventually I want it to be able to write multiple arrays of different custom record types to one single binary file.

For that reason I thought I would write an integer first being the number of bytes that the next array will be in total. Then I write the array itself. I can then read the first integer type block - to tell me the size of the next blocks to read in directly to an array.

For example - when writing the binary file I would do something like this:

    assignfile(f,MasterFileName);
      {$I-}
      reset(f,1);
      {$I+}
      n := IOResult;
      if n<> 0 then
      begin
          {$I-}
          rewrite(f);
          {$I+}
      end;
      n:= IOResult;
      If n <> 0 then
      begin
         writeln('Error creating file: ', n);
      end
      else
      begin
      SetLength(MyArray, 2);

      MyArray[0].ID := 101;
      MyArray[0].Att1 := 'Hi';
      MyArray[0].Att2 := 'MyArray 0 - Att2';
      MyArray[0].Value := 1;

      MyArray[1].ID := 102;
      MyArray[1].Att1:= 'Hi again';
      MyArray[1].Att2:= MyArray 1 - Att2';
      MyArray[1].Value:= 5;

      SizeOfArray := sizeOf(MyArray);

      writeln('Size of character array: ', SizeOfArray);
      writeln('Size of integer var: ', sizeof(SizeOfArray));

      blockwrite(f,sizeOfArray,sizeof(SizeOfArray),actual);

      blockwrite(f,MyArray,SizeOfArray,actual);

      Close(f);

Then you could re-read the file with something like this:

Assign(f, MasterFileName);
Reset(f,1);
blockread(f,SizeOfArray,sizeof(SizeOfArray),actual);
blockread(f,MyArray,SizeOfArray,actual);

Close(f); 

This has the idea that after these blocks have been read that you can then have a new integer recorded and a new array then saved etc.

It reads the integer parts of the records in but nothing for the strings. The record would be something like this:

TMyType = record
ID : Integer;
att1 : string;
att2 : String;
Value : Integer;
end;

Any help gratefully received!!

Upvotes: 0

Views: 525

Answers (2)

Rudy Velthuis
Rudy Velthuis

Reputation: 28806

You have a few mistakes.

MyArray is a dynamic array, a reference type (a pointer), so SizeOf(MyArray) is the size of a pointer, not the size of the array. To get the length of the array, use Length(MyArray).

But the bigger problem is saving long strings (AnsiStrings -- the usual type to which string maps --, WideStrings, UnicodeStrings). These are reference types too, so you can't just save them together with the record. You will have to save the parts of the record one by one, and for strings, you will have to use a function like:

procedure SaveStr(var F: File; const S: AnsiString);
var
  Actual: Integer;
  Len: Integer;
begin
  Len := Length(S);
  BlockWrite(F, Len, SizeOf(Len), Actual);
  if Len > 0 then
  begin
    BlockWrite(F, S[1], Len * SizeOf(AnsiChar), Actual);
  end;
end;

Of course you should normally check Actual and do appropriate error handling, but I left that out, for simplicity.

Reading back is similar: first read the length, then use SetLength to set the string to that size and then read the rest.

So now you do something like:

Len := Length(MyArray);
BlockWrite(F, Len, SizeOf(Len), Actual);
for I := Low(MyArray) to High(MyArray) do
begin
  BlockWrite(F, MyArray[I].ID, SizeOf(Integer), Actual);
  SaveStr(F, MyArray[I].att1);
  SaveStr(F, MyArray[I].att2);
  BlockWrite(F, MyArray[I].Value, SizeOf(Integer), Actual);
end;
// etc...  

Note that I can't currently test the code, so it may have some little errors. I'll try this later on, when I have access to a compiler, if that is necessary.


Update

As Marco van de Voort commented, you may have to do:

rewrite(f, 1);

instead of a simple

rewrite(f);

But as I replied to him, if you can, use streams. They are easier to use (IMO) and provide a more consistent interface, no matter to what exactly you try to write or read. There are streams for many different kinds of I/O, and all derive from (and are thus compatible with) the same basic abstract TStream class.

Upvotes: 1

TMyType = record
ID : Integer;
att1 : string;   // <- your problem

That field att1 declared as string that way means that the record contains a pointer to the actual string data (att1 is really a pointer). The compiler manages this pointer and the memory for the associated data, and the string can be any (reasonable) length.

A quick fix for you would be to declare att1 something like string[64], for example: a string which can be at maximum 64 chars long. That would eliminate the pointer and use the memory of the record (the att1 field itself, which now is a special static array) as buffer for string characters. Declaring the maximum length of the string, of course, can be slightly dangerous: if you try to assign the string a string too long, it will be truncated.

To be really complete: it depends on the compiler; some have a switch to make your declaration "string" usable, making it an alias for "string[255]". This is not the default though. Consider also that using string[...] is faster and wastes memory.

Upvotes: 1

Related Questions