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?

Zamrony P. Juhara
  • 5,222
  • 2
  • 24
  • 40
dmillam
  • 373
  • 1
  • 4
  • 8

7 Answers7

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.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • 1
    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) – Davy Landman Apr 09 '09 at 08:39
  • typecasting it to a PChar should not create any problems. – Davy Landman Apr 09 '09 at 12:52
  • Pointer is compatible with everything. The only reason you'd need to type-cast is if the compiler had trouble with with overload resolution. – Rob Kennedy Apr 09 '09 at 14:18
  • Nice one Rob. I was an underappreciator of SetString. – Caleb Hattingh Nov 17 '09 at 13:24
  • 3
    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. – Gabriel Jun 26 '10 at 19:07
  • @RobKennedy I check it in 10.3 and works good.. tanks – M.Vakili Jun 25 '21 at 09:35
20

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;

John Thomas
  • 4,075
  • 3
  • 29
  • 41
  • 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? – The_Fox Apr 09 '09 at 06:51
16

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;
jonjbar
  • 3,896
  • 1
  • 25
  • 46
Nick Hodges
  • 16,902
  • 11
  • 68
  • 130
  • "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. – jonjbar Jun 22 '11 at 10:11
  • 9
    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. – Remy Lebeau May 05 '12 at 17:06
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.

Gad D Lord
  • 6,620
  • 12
  • 60
  • 106
  • 2
    `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. – Remy Lebeau Dec 31 '14 at 02:03
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

Feedforward
  • 4,521
  • 4
  • 22
  • 34
2

I have not upgraded yet, but my understanding is:

NewCapacity := (M.Size + 1) * SizeOf(Char);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Loren Pechtel
  • 8,945
  • 3
  • 33
  • 45
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;
The Bitman
  • 1,279
  • 1
  • 11
  • 25