Delphi保存/加载动态数组失败

时间:2016-02-15 08:30:10

标签: arrays delphi firemonkey records delphi-xe8

我认为这看起来像是做我的作业'这是一个问题,但我仍然使用 复制代码,使用它并尝试理解它'阶段,这是我发布这个主题的最活跃的事情。

我有一个记录:

type
  Card = record
    Name: string;
    Up,Right,Left,Down,Mark: Single; 
    IDNumber: Integer;
    end;

该记录的数组:

var
  ArrayCard: array of Card;

我想知道如何在文件中存储/加载这种动态数组。

尝试使用这段代码:http://www.pascalgamedevelopment.com/showthread.php?6319-save-load-a-dynamic-array 像这样:

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;

到目前为止它似乎工作,但后来我尝试加载它:

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;

我在模块中得到了一个例外EAccessViolation ......

然后我也尝试过这样的事情:delphi Save and Load dynamic array 它使用正确数量的项加载数组,但它们都是空的或空白的:

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;

2 个答案:

答案 0 :(得分:5)

不要对包含“普通”字符串的记录使用缓冲区操作。 shortstring不是string,而只是指向字符串内容的指针。因此,您的代码只保存并加载指向字符串内容的指针,而不是字符串。如果加载的值指向无法访问的内存,则可能会出现“访问冲突”。 将代码更改为单独保存并加载记录中的变量,如下所示:

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;

<强>更新: 你读过关于泛型的吗?您可以使用TList并在其中加载/保存记录,而不是动态数组:

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;

答案 1 :(得分:1)

在一些帮助下,我设法重新编写代码以使其正常工作。 首先,我需要使string类似string[20],但无法为Android编译,所以我修改了我的记录以使用array of char这样:

type
  EventString= array [0..20] of Char;

  Card = record
    Name:  EventString; //string[20]
    Up,Right,Left,Down,Mark: Single;
    IDNumber: Integer;
    end; 

然后我修改了我的保存/加载程序以使用TFileStream而不是TStream和for循环:

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;

现在它的工作正常。