I think this will look like 'do my homework' kind of a question, but I'm still at the 'copy code, use it and try to understand it' phase, and this is the most active thing I know of for posting questions of this theme.
I have a record:
type
Card = record
Name: string;
Up,Right,Left,Down,Mark: Single;
IDNumber: Integer;
end;
And array of that record:
var
ArrayCard: array of Card;
And I wanted to know how can a dynamic array of this kind be stored/loaded to/from a file.
Tried using this piece of code: http://www.pascalgamedevelopment.com/showthread.php?6319-save-load-a-dynamic-array
like this:
Procedure TMainFrom.WriteMyData;
Var
FS : TFileStream;
I,iSize : Integer;
TmpPath: string;
Begin
TmpPath:= TPath.Combine(TPath.GetDocumentsPath, 'Cards.dat');
FS := TFileStream.Create(TmpPath, fmOpenWrite);
iSize:= Length(ArrayCard);
FS.WriteBuffer(iSize,SizeOf(iSize));
For I := 0 To iSize - 1 Do
FS.WriteBuffer(ArrayCard[I],SizeOf(Card));
FS.Free;
End;
An it seems to work so far, but then I try to load it like this:
Procedure TMainFrom.ReadMyData;
Var
FS : TFileStream;
I,iSize : Integer;
TmpPath: string;
TempCard : Card;
Begin
TmpPath:= TPath.Combine(TPath.GetDocumentsPath, 'Cards.dat');
FS := TFileStream.Create(TmpPath, fmOpenRead);
FS.ReadBuffer(iSize,SizeOf(iSize));
SetLength(ArrayCard,iSize);
For I := 0 To iSize - 1 Do
Begin
FS.ReadBuffer(TempCard,SizeOf(Card));
ArrayCard[I] := TempCard; //It Breaks Here...The Watch List: TempCard Inaccessible value
End;
FS.Free;
End;
And I get a Exception EAccessViolation in module...
Then I also tried something like this: delphi Save and Load dynamic array
It loads the array with the correct amount of items, but they are all empty or blank:
procedure TMainFrom.SaveCardsToFile;
var
Stream: TStream;
L: Integer;
TmpPath: string;
ICount: Integer;
strm: TMemoryStream;
begin
TmpPath:= TPath.Combine(TPath.GetDocumentsPath, 'Cards.bin');
Stream:= TFileStream.Create(TmpPath, fmCreate);
L:= Length(ArrayCard);
Stream.WriteBuffer(L, SizeOf(L));
Stream.WriteBuffer(Pointer(ArrayCard)^, L * SizeOf(Card));
Stream.Free;
end;
procedure TMainFrom.LoadCardsFromFile;
var
Stream: TStream;
L: LongWord;
TmpPath: string;
begin
TmpPath:= TPath.Combine(TPath.GetDocumentsPath, 'Cards.bin');
Stream:= TFileStream.Create(TmpPath, fmOpenRead);
Stream.ReadBuffer(L, SizeOf(L));
SetLength(ArrayCard, L);
Stream.ReadBuffer(Pointer(ArrayCard)^, L * SizeOf(Card));
Stream.Free;
end;
Do not use buffer operations with records which contains "normal" strings. Instead of shortstring
, string
is only pointer to string content. So, your code saves and loads only pointer to string content, not string. You can get Access Violation if loaded value points to unreachable memory.
Change your code to separately save and load variables in record, like this:
type
TmyRec = record
str: string;
i: integer;
procedure SaveToStream(Stream: TStream);
procedure LoadFromStream(Stream: TStream);
end;
{ myRec }
procedure TmyRec.LoadFromStream(Stream: TStream);
var
strLen: integer;
strBuf: TBytes;
begin
Stream.Read(strLen, SizeOf(Integer));
SetLength(strBuf, strLen);
Stream.Read(strBuf, strLen);
str:=TEncoding.UTF8.GetString(strBuf);
Stream.Read(i, SizeOf(Integer));
end;
procedure TmyRec.SaveToStream(Stream: TStream);
var
strBuf: TBytes;
strLen: integer;
begin
// direct set encoding type helps to avoid problems with different platforms.
// for example, Windows uses UCS2, Android and iOS - UTF8 encoding
strBuf:=TEncoding.UTF8.GetBytes(str);
strLen:=Length(strBuf);
Stream.Write(strLen, SizeOf(Integer));
Stream.Write(strBuf, strLen);
Stream.Write(i, SizeOf(Integer));
end;
Update:
do you read about generics? Instead of dynamic array, you can use TList and load/save records in it:
type
TmyRecList = class(TList<TmyRec>)
public
procedure SaveToStream(Stream: TStream);
procedure LoadFromStream(Stream: TStream);
end;
{ TmyRecList }
procedure TmyRecList.LoadFromStream(Stream: TStream);
var
Len: integer;
i: Integer;
rec: TmyRec;
begin
Clear;
Stream.Read(Len, SizeOf(integer));
for i := 0 to Len-1 do
begin
Rec.LoadFromStream(Stream);
Add(rec);
end;
end;
procedure TmyRecList.SaveToStream(Stream: TStream);
var
i: Integer;
begin
Stream.Write(Count, SizeOf(Integer));
for i := 0 to Count-1 do
Items[i].SaveToStream(Stream);
end;
procedure THeaderFooterForm.FormCreate(Sender: TObject);
var
Stream: TStream;
rec: TmyRec;
recList: TmyRecList;
begin
Stream:=TMemoryStream.Create;
try
recList:=TmyRecList.Create;
try
rec.str:='sample text';
rec.i:=123;
recList.Add(rec);
rec.str:='another text';
rec.i:=234;
recList.Add(rec);
recList.SaveToStream(Stream);
Stream.Seek(0, soBeginning);
recList.LoadFromStream(Stream);
ShowMessage('this is str value in second record: ' + recList[1].str);
finally
recList.Free;
end;
finally
Stream.Free;
end;
With some help, I have managed to remake my code to work properly.
Firstly I needed to make the string
something like string[20]
but that couldn't compile for android so I modified my record to use array of char
like this:
type
EventString= array [0..20] of Char;
Card = record
Name: EventString; //string[20]
Up,Right,Left,Down,Mark: Single;
IDNumber: Integer;
end;
Then I modified my Saving/Loading procedures to use TFileStream instead of TStream and a for loop:
procedure TMainFrom.SaveCardsToFile;
var
Stream: TFileStream;
L: Integer;
TmpPath: string;
n: Integer;
begin
TmpPath:= TPath.Combine(TPath.GetDocumentsPath, 'Cards.bin');
Stream:= TFileStream.Create(TmpPath, fmCreate);
L:= Length(ArrayCard)-1;
for n:=0 to L do
begin
Stream.WriteBuffer(ArrayCard[n], SizeOf(Card)); //Saving
end;
Stream.Free;
end;
procedure TMainFrom.LoadCardsFromFile;
var
Stream: TFileStream;
L: LongWord;
TmpPath: string;
n: Integer;
begin
TmpPath:= TPath.Combine(TPath.GetDocumentsPath, 'Cards.bin');
Stream:= TFileStream.Create(TmpPath, fmOpenRead);
SetLength(ArrayCard, Round(Stream.Size/SizeOf(Card)));
L:= Length(ArrayCard)-1;
for n:=0 to L do
begin
Stream.ReadBuffer(ArrayCard[n], SizeOf(Card)); //Loading
ListCard.Items.Add(ArrayCard[n].Name); //Just adds card names to a listbox
end;
Stream.Free;
end;
And now it works just fine.