Converting TMemoryStream to 'String' in Delphi 2009
Asked Answered
P

8

33

We had the following code prior to Delphi 2009:

function MemoryStreamToString(M : TMemoryStream): String;
var
    NewCapacity: Longint;
begin
    if (M.Size = > 0) or (M.Memory = nil) then
       Result:= '' 
    else
    begin
       if TMemoryStreamProtected(M).Capacity = M.Size then
       begin
           NewCapacity:= M.Size+1;
           TMemoryStreamProtected(M).Realloc(NewCapacity);
       end;
       NullString(M.Memory^)[M.Size]:= #0;
       Result:= StrPas(M.Memory);
    end;
end;

How might we convert this code to support Unicode now with Delphi 2009?

Potpourri answered 9/4, 2009 at 3:24 Comment(0)
W
73

The code you have is unnecessarily complex, even for older Delphi versions. Why should fetching the string version of a stream force the stream's memory to be reallocated, after all?

function MemoryStreamToString(M: TMemoryStream): string;
begin
  SetString(Result, PChar(M.Memory), M.Size div SizeOf(Char));
end;

That works in all Delphi versions, not just Delphi 2009. It works when the stream is empty without any special case. SetString is an under-appreciated function.

If the contents of your stream aren't changing to Unicode with your switch to Delphi 2009, then you should use this function instead:

function MemoryStreamToString(M: TMemoryStream): AnsiString;
begin
  SetString(Result, PAnsiChar(M.Memory), M.Size);
end;

That's equivalent to your original code, but skips the special cases.

Witching answered 9/4, 2009 at 8:30 Comment(6)
I've done a lot of Delphi memory stuff, but I hadn't heard about SetString, always used SetLength(dest, length) and a Move(src, @(dest[1]), length); which SetString does as well (it calls _LStrFromPCharLen)Slattery
typecasting it to a PChar should not create any problems.Slattery
Pointer is compatible with everything. The only reason you'd need to type-cast is if the compiler had trouble with with overload resolution.Witching
Nice one Rob. I was an underappreciator of SetString.Markel
First function is not working with D7. The compiler fails at the second parameter ("memory") with the message "incompatible types". So, it needs indeed a pchar typecast.Lichter
@RobKennedy I check it in 10.3 and works good.. tanksAt
G
23

Or perhaps you can refactor your code to use directly a TStringStream directly? You can use it instead of TMemoryStream (they have the same interface) and you can 'convert' it to a string by simply calling myString := myStringStream.DataString;

Grand answered 9/4, 2009 at 5:51 Comment(1)
Indeed, that was the first thing that came to mind. Why not create a TStringStream, load the memorystream in it, and return the datastring?Stasny
E
18

A "cleaner" way might be:

function StreamToString(aStream: TStream): string;
var
  SS: TStringStream;
begin
  if aStream <> nil then
  begin
    SS := TStringStream.Create('');
    try
      SS.CopyFrom(aStream, 0);  // No need to position at 0 nor provide size
      Result := SS.DataString;
    finally
      SS.Free;
    end;
  end else
  begin
    Result := '';
  end;
end;
Euglena answered 9/4, 2009 at 3:24 Comment(2)
"aStream.Position := 0" is not required as if you call "SS.CopyFrom(aStream, 0)", it will position the source at 0 and get its size for you.Thremmatology
You have to be careful with TStringStream in D2009+, as it is TEncoding-aware now. CopyFrom() will copy the raw bytes of the source TStream as-is, but the DataString property getter will decode those bytes using whatever TEncoding is passed to the TStringStream constructor (TEncoding.Default or TEncoding.UTF8 by default, IIRC). So if the TStream encoding does not match the TStringStream encoding, you will get data loss/corruption.Turmoil
S
6

I use:

function StreamToString(const Stream: TStream; const Encoding: TEncoding): string;
var
  StringBytes: TBytes;
begin
  Stream.Position := 0;
  SetLength(StringBytes, Stream.Size);
  Stream.ReadBuffer(StringBytes, Stream.Size);
  Result := Encoding.GetString(StringBytes);
end;

It has been tested with Delphi XE7 only.

Stoup answered 31/12, 2014 at 1:33 Comment(1)
TBytes is a dynamic array. The first parameter of ReadBuffer() is an untyped var You need to dereference the TBytes to get the correct memory address to pass to ReadBuffer(), eg: ReadBuffer(StringBytes[0], ...) or safer ReadBuffer(PByte(StringBytes)^, ...) when Size is 0.Turmoil
M
2

I have not upgraded yet, but my understanding is:

NewCapacity := (M.Size + 1) * SizeOf(Char);
Multipurpose answered 9/4, 2009 at 3:31 Comment(0)
C
2

There's a factor called TStringStream that will be able to assist you. . .you can load the contents of another flow like that:

var StringStream: TStringStream; 
begin StringStream := TStringStream.Create(''); 
StringStream.CopyFrom(OtherStream, OtherStream.Size); 
end;

You can now get into the series for a String kind such as this: The data-string property comprises the series... but do not try so with large objects such as in the event that you load a huge file to some filestream then copy this to your own stringstream and make an effort to produce it cause it arranges a lot of memory!

Hope that helps

Capper answered 10/10, 2018 at 10:44 Comment(0)
C
1

You can cast it into the right sized character pointer and just simple assign it:

procedure getMemoryStreamAsString( aMS_ : TMemoryStream );
var
  ws : widestring; // in newer Delphi it can be string
  ans : ansistring;
begin
  ws := pwidechar( aMS_.memory );
  // OR
  ans := pansichar( aMS_.memory );
end;
Chandigarh answered 19/7, 2019 at 10:42 Comment(0)
A
0

using good old StringList

function StreamToString (Stream : TStream; Encoding : TEncoding) : string;
begin
  var List := TStringList.Create;
  try
    List.LoadFromStream (Stream, Encoding);
    Result := List.Text;
  finally
    List.Free;
  end; 
end; 
  
Aruba answered 26/11, 2023 at 15:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.