[Delphi] Strings in TMemoryStream

Hallo allerseits.

Ich versuche, in D3 eine Serie von Daten in TMemoryStreams zu speichern. U.a. auch Strings. Ich habe es zunächst so versucht:

Procedure WriteStr(Str:String; Mem:TMemoryStream);
var L:integer;
begin
L := Length(Str);
Mem.Write(L,SizeOf(Integer));
Mem.Write(Str,L);
end;

Procedure ReadStr(var Str:String; Mem:TMemoryStream);
var L:integer;
begin
Mem.Read(L,SizeOf(Integer));
Mem.Read(Str,L);
end;

Wenn man einmal WriteStr und später einmal ReadStr aufruft, klappt auch alles. Wenn man aber nochmal ReadStr aufruft, gibts ab der ersten String-Position im Stream Probleme. Wenn auch tausend andere Sachen in den Stream geschrieben werden und ich NUR WriteStr und ReadStr ausklammere, funktioniert alles.
Besonders häufig treten Probleme auf, wenn Strings unterschiedlicher Länge haben.

WARUM ??? Was mache ich falsch ?

Übrigends funktioniert es, wenn ich die chars des Strings EINZELN abspeichere:

Procedure WriteStr(Str:String; Mem:TMemoryStream);
var i,L:integer;
begin
L := Length(Str);
Mem.Write(L,SizeOf(Integer));
for i:=1 to L do Mem.Write(Str[i],SizeOf(Char));
end;

Procedure ReadStr(var Str:String; Mem:TMemoryStream);
var i,L:integer; c:Char;
begin
Mem.Read(L,SizeOf(Integer));
Str := ‚‘;
for i:=1 to L do begin
Mem.Read(c,SizeOf(Char));
str:=str+c;
end;
end;

Ich meine, man muß doch auch den String als Ganzes schreiben und lesen können.

Kann mir da jemand auf die Sprünge helfen ? Danke schonmal.

Jochen

Du solltest sorgfältiger mit Zeiger umgehen ! Denn ein String ist nichts weiteres als ein Pointer auf ein Array of Char.

Hier deine Procs nochmal:

Procedure WritrStr(Str: string; Buffer: TStream);

var
L: Integer;

begin
L := Length(Str);
Buffer.Write(L, 4);
Buffer.Write(Pointer(Str)^, L);
end;

Procedure ReadStr(var Str:string; Mem:TStream);
var
L:integer;

begin
Mem.Read(L,SizeOf(Integer));
Mem.Read(Str,L);
end;

Nachtrag
Damit du bei unterschiedlichen String-Längen keine Überaschungen erlebst, hier eine stabile Version mit SetLength (hoffe das gab’s in D3 auch schon, ich hab halt 5 hier)

Procedure ReadStr(Str: string; Buffer: TStream);

var
L : Integer;

begin
Buffer.Read(L, 4);
SetLength(Str, L);
Buffer.Read(Pointer(Str)^, L);
end;

Zwar immernoch nicht perfekt, aber für den Anfang ned schlecht :smile:

Danke schön, ich werd’s ausprobieren.
Ich hatte es mit einem PChar-Typecast versucht, aber ohne die Ihnaltsreferenz (den circumflex). Interesanterweise wurde dann schon immer ein Teil des Strings gespeichert. Aber immer irgendwie verkrüppelt.

Grüße

Jochen

Ja, Setlength ist überhaupt 'ne gute Idee (gibt’s auch in D3). Wahrscheinlich kann man aus Typecasten verzichten, wenn man vorher Setlength benutzt. Ich wer’s ausprobieren.

Wunnebah

Jochen

Ja kann man. Wenn du den richtigen String.-Typ verwnedest, sind deine Datenfiles dann auch kompatibel mit anderen Loadern die in C oder VB gecoded wurden

hmm, es kalppt leider nicht so.
Mit dem Pointer gibts 'ne Schutzverletzung, mit PChar macht er’s auch nicht richtig.

Ich habe aber eine - etwas umständliche - Lösung gefunden, die zu funktionieren scheint:

TObj = class
s: String;
x: Integer;
constructor Create(ss:String; xx:Integer);
Procedure WriteToStream(mem:TStream);
Procedure ReadFromStream(mem:TStream);
function Data:String;
end;

Procedure TObj.WriteToStream(mem:TStream);
var L:Integer; p:stuck_out_tongue:Char;
begin
L := Length(s)+1;
Getmem(p,L);
StrPCopy(p,s);
mem.Write(L,SizeOf(integer));
mem.Write(p^,L);
mem.write(x,SizeOf(integer));
FreeMem§;
end;

Procedure TObj.ReadFromStream(mem:TStream);
var L:Integer; p:stuck_out_tongue:Char;
begin
mem.Read(L,SizeOf(integer));
GetMem(p,L);
mem.Read(p^,L); // L ist jetzt nicht mehr definiert !!!
mem.read(x,SizeOf(integer));
s:=StrPas§;
FreeMem§;
end;


// Test:

const n=10;
var i:integer; mem:TMemoryStream;
begin
// zwei Listen anlegen
List1:=TList.create;
List2:=TList.create;
for i:=1 to n do
List1.add(TObj.create(‚Original‘,i));
for i:=1 to n do
List2.add(TObj.create(‚Kopie‘,i));
// die Objektdataen von List1 in die Objekte von List2 kopieren:
mem := TMemoryStream.create;
for i:= 0 to List1.count-1 do TObj(List1[i]).WriteToStream(mem);
mem.Position:=0;
for i:= 0 to List2.count-1 do TObj(List2[i]).ReadFromStream(mem);
mem.Free;
for i:= 0 to List1.count-1 do TObj(List1[i]).s := ‚Neu‘;
end;

Beachte: SetLength braucht man nicht und eigenartigerweise ist die Variable L nach dem Ausfruf von

mem.Read(p^,L);

nicht mehr definiert (sowas um 127342…).
Hab ich zufällig beim debuggen bemerkt. Ist das ein Bug in D3 ?

Beste Grüße

Jochen

Delphi 3 hatte noch den einen oder anderen Bug, schon möglich, solltest langsam upgraden :smile:

Mit TFileStream mach ich’s immer mit SetLength und Pointer-Dereferenzierung, läuft ohne Probs. Musst nur darauf achten, dass der String immer initialisiert ist und das - wenn du mehr Datenplatz brauchst - diesen auch mit SetLength anforderst