0

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!!

2
  • 2
    A string is not what you think it is. You need to read the documentation. Commented Jul 15, 2018 at 22:54
  • 1
    And SizeOf(MyArray) returns the size of a pointer. Commented Jul 16, 2018 at 4:39

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Superb - I will work on that later - out of interest, whereas I will never need thousands of characters, I may well need more than 255 (perhaps 500 roughly). Could I use AnsiString[500]? Thank you so much
@PaulCurran string[500]: it depends on compiler, but very probably not. Note that "ansistring" is not "string". Classic pascal strings have a maximum length, while Ansistring, in contrast, do not - the name implies they are very different from "string", and the biggest difference is they are no more static arrays (i.e., your problem).
There is no AnsiString[..]. There is no string[500] either. The first byte of a so called counted string (or "Pascal string" as Apple calls it) is the length, so it can never be more than 255. But you can use a fixed size array[0..499] of AnsiChar, although that will be more work and wastes even more memory. You can use an AnsiString, but then you'll have to save it manually. Should be easy to write a routine for that (and for re-loading).
1

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.

5 Comments

@Marco: I assume that was done earlier on. I did not write the file opening code. I assume it was done at the beginning, like in the original code. Or did you specifically mean the ,1 part? I must admit that I haven't used old style I/O for a long time anymore, so that may be necessary, I don't know. I would use streams.
In this day and age of text fileformats I think I use language I/O more than ever. And yes, sorry it should have been a comment to the question. I was checking your answer to see if it already contained the hint, and accidentally commented it there.
I don't know about FP and Lazarus, but in Delphi you have excellent TextWriter/Reader classes. Language I/O is rather quirky, IMO, while streams are far more consistent. I do use Writeln a lot too, but only in simple console test programs (testing SO questions or some assumptions about how to implement something), outputting directly to the console.
@RudyVelthuis - thank you so much for the advice. re. streams if I were to do it that way, does that give me more flexibility regarding writing strings larger than 255 chars as part of a record? And then use something along the lines of: fsIn := TFileStream.Create(MasterFileName, fmOpenRead); fsIn.Read(SizeOfArray, sizeof(SizeOfArray)); fsIn.Read(MyArray, SizeOfArray); fsin.free();
No matter if you use streams or old I/O, you will have to save your record fields one by one, integers as such, strings as such. AnsiStrings are mere pointers, and you should never save pointers. So you'll have to resort to something like SaveStr after all. OK, you could use several array[0..599] of AnsiChar in the record, and then you could save the records in one fell swoop, but that would waste a lot of space, if many of the strings are rather short. And it would not work for strings longer than 600 items either. You choose.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.