Marek Jedliński
Marek Jedliński

Reputation: 7316

Converting a null-terminated memory stream to unicode string

In Delphi XE, I am capturing CF_UNICODETEXT data from the clipboard. The result is a stream that terminates with two null bytes. To get the actual string that was copied to clipboard, I need to strip the nulls.

This similar so question contains a nice method of converting from TMemoryStream to Delphi's unicode string:

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

In my case, however, this would produce a string including the trailing nulls. I could fix that by limiting the size:

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

... but this feels ugly, "special-casey". I wonder if there is a cleaner way to code this, so that anyone (me!) looking at the code later won't immediately ask "Why is the trailing char being dropped from the stream?"

Edit: One way of pre-empting the question is adding a comment. But, other than that?

Upvotes: 1

Views: 4166

Answers (2)

Alex
Alex

Reputation: 5658

If you target CF_UNICODETEXT, you need to specify unicode string specifically:

// For old Delphi versions
{$IFNDEF UNICODE}
type
  UnicodeString = WideString;
{$ENDIF}

// For CF_TEXT
function MemoryStreamToAnsiString(M: TMemoryStream): AnsiString;
begin
  SetString(Result, M.Memory, M.Size);
  if (Result <> '') and (Result[Length(Result)] = #0) then
    SetLength(Result, Length(Result) - 1);
end;

// For CF_UNICODETEXT
function MemoryStreamToUnicodeString(M: TMemoryStream): UnicodeString;
begin
  SetString(Result, M.Memory, M.Size div SizeOf(WideChar));
  if (Result <> '') and (Result[Length(Result)] = #0) then
    SetLength(Result, Length(Result) - 1);
end;

// I'm not sure that you should use this form
function MemoryStreamToString(M: TMemoryStream): String;
begin
  SetString(Result, M.Memory, M.Size div SizeOf(Char));
  if (Result <> '') and (Result[Length(Result)] = #0) then
    SetLength(Result, Length(Result) - 1);
end;

If you're 100% sure that string is zero-terminated, then:

// For CF_TEXT
function MemoryStreamToAnsiString(M: TMemoryStream): AnsiString;
begin
  SetString(Result, M.Memory, M.Size - 1);
end;

// For CF_UNICODETEXT
function MemoryStreamToUnicodeString(M: TMemoryStream): UnicodeString;
begin
  SetString(Result, M.Memory, (M.Size div SizeOf(WideChar)) - 1);
end;

function MemoryStreamToString(M: TMemoryStream): String;
begin
  SetString(Result, M.Memory, (M.Size div SizeOf(Char)) - 1);
end;

Upvotes: 2

David Heffernan
David Heffernan

Reputation: 613491

What's wrong with Clipboard.AsText? It does everything for you with no need for streams, poking at bytes, dealing with null terminators etc.

As for the precise question you raised, I would simply write:

SetString(Result, M.Memory, M.Size div SizeOf(Result[1]) - 1);

Upvotes: 5

Related Questions