PDA

View Full Version : save/load a dynamic array



hello
06-11-2010, 02:36 AM
Hi, the save-procedure saves obviously 4 records, but the load-proc loads just one from the file test.DAT.
First thought might be that the load-proc doesn't work correctly, but the file test.MAP is read properly.
Second thought might be the save-proc isn't well programmed. I can't figure it out. I apriciate any valid hint.
Note: --- Manage Attachments --- isn't working so here is the code, if you need all files feel free to write to guido-lang@gmx.de
Thanks in advance


unit FMain;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Buttons;

const
REC_COUNT = 4;

type
TRMap = record
X, Y, Index: Integer;
end;

TForm1 = class(TForm)
LoadDat: TBitBtn;
Save: TButton;
ListBox1: TListBox;
LoadMap: TButton;
procedure LoadDatClick(Sender: TObject);
procedure SaveClick(Sender: TObject);
procedure LoadMapClick(Sender: TObject);
private
{ Private declarations }
FS: TFileStream;
ARMap: array of TRMap;
public
{ Public declarations }
end;

var
Form1: TForm1;


implementation

{$R *.DFM}

procedure TForm1.LoadDatClick(Sender: TObject);
var
i, FileSize: Integer;
begin
try
FS := TFileStream.Create(ExtractFilePath(Paramstr(0)) + 'test.dat', fmOpenRead);
except
ShowMessage('Couldn''t open file.');
Exit;
end;
FS.ReadBuffer(FileSize, SizeOf(FileSize));
// Fs.ReadBuffer(FileSize, FS.Size div SizeOf(Integer));
SetLength(ARMap, FileSize);
FS.ReadBuffer(ARMap[0], SizeOf(TRMap) * FileSize);

Listbox1.Clear;
for i := 0 to FileSize-1 do
begin
{
FS.ReadBuffer(ARMap[i].X, SizeOf(ARMap[i].X));
FS.ReadBuffer(ARMap[i].Y, SizeOf(ARMap[i].Y));
FS.ReadBuffer(ARMap[i].Index, SizeOf(ARMap[i].Index));
}
with Listbox1.Items do
begin
Add(' ');
Add(' *** Record NO: ' + InttoStr(i));
Add(' ');
Add('ARMap['+ InttoStr(i) +'].X: ' + InttoStr(ARMap[i].X));
Add('ARMap['+ InttoStr(i) +'].Y: ' + InttoStr(ARMap[i].Y));
Add('ARMap['+ InttoStr(i) +'].Index: ' + InttoStr(ARMap[i].Index));
end; // with
end; // for
FS.Free;
end; // procedure


procedure TForm1.SaveClick(Sender: TObject);
var
i, liX, liY, liL: Integer;
begin
SetLength(ARMap, REC_COUNT);
FS := TFileStream.Create(ExtractFilePath(Paramstr(0)) + 'test.dat', fmCreate);
// save record number
// FS.WriteBuffer(ARMap, SizeOf(Length(ARMap)));

Listbox1.Clear;

// for i := 0 to REC_COUNT-1 do
for i := 0 to Length(ARMap)-1 do
begin
ARMap[i].X := (i+1);
ARMap[i].Y := (i+1);
ARMap[i].Index := (i+1);

with Listbox1.Items do
begin
Add(' ');
Add(' *** Record NO: ' + InttoStr(i));
Add(' ');
Add('ARMap['+ InttoStr(i) +'].X: ' + InttoStr(ARMap[i].X));
Add('ARMap['+ InttoStr(i) +'].Y: ' + InttoStr(ARMap[i].Y));
Add('ARMap['+ InttoStr(i) +'].Index: ' + InttoStr(ARMap[i].Index));
end; // with

// just one rec: FS.WriteBuffer(ARMap[i], SizeOf(ARMap[i]));
{ just one rec
FS.WriteBuffer(ARMap[i].X, SizeOf(ARMap[i].X));
FS.WriteBuffer(ARMap[i].Y, SizeOf(ARMap[i].Y));
FS.WriteBuffer(ARMap[i].Index, SizeOf(ARMap[i].Index));
} end; // for

// produces rubish which isn't loaded: FS.WriteBuffer(ARMap, SizeOf(ARMap));

// just one rec:
FS.WriteBuffer(ARMap[0], SizeOf(TRMap) * REC_COUNT);
// just one rec: FS.WriteBuffer(Pointer(ARMap)^, Length(ARMap)* SizeOf(Integer));

FS.Free;
ARMap := nil;
end;


procedure TForm1.LoadMapClick(Sender: TObject);
var
i, FileSize: Integer;
begin
try
FS := TFileStream.Create(ExtractFilePath(Paramstr(0)) + 'test.map', fmOpenRead);
// FS := TFileStream.Create(ExtractFilePath(Paramstr(0)) + 'test.dat', fmOpenRead);
except
ShowMessage('Couldn''t open file.');
Exit;
end;
FS.ReadBuffer(FileSize, SizeOf(FileSize));
// Fs.ReadBuffer(FileSize, FS.Size div SizeOf(Integer));
SetLength(ARMap, FileSize);
FS.ReadBuffer(ARMap[0], SizeOf(TRMap) * FileSize);

Listbox1.Clear;
for i := 0 to FileSize-1 do
begin
{ is working as well, but more mess
FS.ReadBuffer(ARMap[i].X, SizeOf(ARMap[i].X));
FS.ReadBuffer(ARMap[i].Y, SizeOf(ARMap[i].Y));
FS.ReadBuffer(ARMap[i].Index, SizeOf(ARMap[i].Index));
}

with Listbox1.Items do
begin
Add(' ');
Add(' *** Record NO: ' + InttoStr(i));
Add(' ');
Add('ARMap['+ InttoStr(i) +'].X: ' + InttoStr(ARMap[i].X));
Add('ARMap['+ InttoStr(i) +'].Y: ' + InttoStr(ARMap[i].Y));
Add('ARMap['+ InttoStr(i) +'].Index: ' + InttoStr(ARMap[i].Index));
end; // with
end; // for
FS.Free;
end;

end.

Dan
06-11-2010, 04:18 AM
first of all use code tags when you're inserting large parts of the code. the way it is here will strip people of any desire to help you.
there is a lot of commented code which is unnecesary here and I assume it confuses you as well.
for example:
you read the number of records here:
FS.ReadBuffer(FileSize, SizeOf(FileSize));

but you never write it.

so my advice is clean you code, work through it step by step and you will surely fix all your problems.

Murmandamus
06-11-2010, 10:10 PM
Saving/Loading a dynamic array on a stream pretty much amounts to:

1) Writing the array elements count onto the stream.
2) Looping through the array and writing out each element's contents, fully dereferenced, to the stream. That means the element and any part of its content has to contain no non-dereferenced pointers. Stream I/O functions expect the variables they are passed to not be or contain pointers; otherwise, you'll just store the pointer values, rather than the data in what the pointers point to.

More or less, you're handing an untyped bucket of data to the stream write function, and it doesn't know or care what the data actually is inside the bucket. Likewise, when you read data from a stream, you pass an empty bucket and how much untyped data you want to fill it with.

Also, the reads have to exactly mirror/balance the writes, or Things Go Bad (tm).

With the simple record structure you are using (all well-defined static scalar types), you can just write/read it straight up. However, if you put a string or other dynamic type into that record later, you'll have to add code in the element write function to "descend into" those dynamic types and read/write them appropriately dereferenced.

In terms of code:


Type
TMyDataRecord = Record
X,Y,Index : Integer;
End;

Var
AMyDataArray = Array Of TMyDataRecord;

Procedure WriteMyData;

Var
FS : TFileStream;
I,iCount : Integer;

Begin
FS := TFileStream.Create(ExtractFilePath(Paramstr(0)) + 'test.dat', fmOpenWrite);
iSize = Length(AMyDataArray);
FS.WriteBuffer(iSize,SizeOf(iSize));
For I := 0 To iSize - 1 Do
FS.WriteBuffer(AMyDataArray[I],SizeOf(TMyDataRecord));
FS.Free;
End;

Procedure ReadMyData;

Var
FS : TFileStream;
I,iCount : Integer;
RData : TMyDataRecord;

Begin
FS := TFileStream.Create(ExtractFilePath(Paramstr(0)) + 'test.dat', fmOpenRead);
FS.ReadBuffer(iSize,SizeOf(iSize));
SetLength(AMyDataArray,iSize);
For I := 0 To iSize - 1 Do
Begin
FS.ReadBuffer(RData,SizeOf(TMyDataRecord));
AMyDataArray[I] := RData;
End;
FS.Free;
End;


It has no error handling, but that's basically what you have to do.

User137
07-11-2010, 12:01 PM
Like Dan pointed out, the code should only be missing this from the saving part:


FS.WriteBuffer(integer(REC_COUNT), SizeOf(integer));

But if the record count is always same constant its not even necessary to read FileSize if it is always REC_COUNT.