Home > Back-end >  Unexpected behaviour reusing a TMemoryStream in Delphi
Unexpected behaviour reusing a TMemoryStream in Delphi

Time:04-15

I am trying to read two strings of varying length from a TMemoryStream, but both streams end up being the same length. So if, for example, the first string is 'abcdefghijkl', and the second one is 'wxyz', the value I get for the second string is 'wxyzefghijkl' (the first four characters of my new string ('wxyz') followed by the remaining characters of the 1st string that have not been replaced by 'wxyz'

My code is:-

var
  L : LongInt
  S : string;

...
  msRecInfo.Position := 0;
  msRecInfo.Read(L, SizeOf(L)); // read size of following string ...

  SubStream.Clear;
  SubStream.CopyFrom(msRecInfo, L); // copy next block of data to a second TMemoryStream

  if (L > 0) then S := StreamToString(SubStream);  //convert the stream into a string

  msRecInfo.Read(L, SizeOf(L)); // get size of following string ...
  SubStream.CopyFrom(msRecInfo, L);
  if (L > 0) then S := StreamToString(SubStream);

I have been battling with this for hours without success. Can anyone point out what I am doing wrong?

CodePudding user response:

You are not calling SubStream.Clear() before the 2nd call to SubStream.CopyFrom(). So, the 1st call to StreamToString(SubStream) leaves SubStream.Position at the end of the stream, then the subsequent SubStream.CopyFrom() adds more data to the stream, preserving the existing data. Then the subsequent StreamToString(SubStream) reads all of the data from SubStream.

Also, be aware that if L is 0 when you pass it to SubStream.CopyFrom(), it will copy the entire msRecInfo stream. This is documented behavior:

https://docwiki.embarcadero.com/Libraries/en/System.Classes.TStream.CopyFrom

If Count is 0, CopyFrom sets Source position to 0 before reading and then copies the entire contents of Source into the stream. If Count is greater than or less than 0, CopyFrom reads from the current position in Source.

So, you need to move up your L > 0 check, eg:

msRecInfo.Read(L, SizeOf(L));
if (L > 0) then
begin
  SubStream.Clear;
  SubStream.CopyFrom(msRecInfo, L);
  S := StreamToString(SubStream);
end
else
  S := '';

I would suggest wrapping this logic into a reusable function, eg:

var
  L : LongInt;
  S : string;

  function ReadString: string;
  begin
    msRecInfo.Read(L, SizeOf(L)); // read size of following string ...
    if (L > 0) then
    begin
      SubStream.Clear;
      SubStream.CopyFrom(msRecInfo, L); // copy next block of data to a second TMemoryStream
      Result := StreamToString(SubStream);  //convert the stream into a string
    end else
      Result := '';
  end;
begin
  ...
  msRecInfo.Position := 0;
  S := ReadString;
  S := ReadString;
  ...

Although, if feasible, I would suggest just getting rid of SubStream altogether, update StreamToString() to take L as an input parameter, so that you can read the string from msRecInfo directly, eg:

msRecInfo.Read(L, SizeOf(L));
S := StreamToString(msRecInfo, L);

No need for a 2nd TMemoryStream if you can avoid it.

  • Related