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.