Преобразование потока памяти с завершающим нулем в строку Unicode

В Delphi XE я собираю данные CF_UNICODETEXT из буфера обмена. Результатом является поток, который заканчивается двумя нулевыми байтами. Чтобы получить фактическую строку, скопированную в буфер обмена, мне нужно удалить нули.

Этот аналогичный вопрос содержит хороший метод преобразования TMemoryStream в Delphi строка Юникода:

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

Однако в моем случае это приведет к появлению строки, включающей завершающие нули. Я мог бы исправить это, ограничив размер:

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

... но это кажется уродливым, «особенным». Интересно, есть ли более чистый способ закодировать это, чтобы кто-либо (я!), Посмотревший код позже, не сразу спросил: «Почему завершающий символ удаляется из потока?»

Изменить. Один из способов предварять вопрос - это добавить комментарий. Но кроме этого?


person Marek Jedliński    schedule 19.01.2011    source источник
comment
Забавно, что вы упомянули TMemoryStream. Сегодня я удалил его из всей своей кодовой базы (осталось не так много ссылок) из-за реализации фрагментации памяти!   -  person David Heffernan    schedule 19.01.2011
comment
@ Дэвид: это интригует. Какой заменитель вы предпочитаете?   -  person Marek Jedliński    schedule 20.01.2011
comment
моя собственная ручная работа TBlockAllocatedMemoryStream = class(TStream) выполняет свою работу без фрагментации. Он также не требует непрерывного адресного пространства и позволяет избежать еще одной ловушки с TMemoryStream.   -  person David Heffernan    schedule 20.01.2011


Ответы (2)


Если вы настроили таргетинг на CF_UNICODETEXT, вам необходимо указать строку unicode:

// 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;

Если вы на 100% уверены, что строка заканчивается нулем, тогда:

// 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;
person Alex    schedule 20.01.2011
comment
Что ж, OP использует XE, и предполагается, что он не пытается писать код, который компилируется в других версиях Delphi. Таким образом, это не должно быть таким сложным. - person David Heffernan; 20.01.2011

Что не так с Clipboard.AsText? Он делает все за вас, не требуя потоков, выковыривания байтов, работы с нулевыми терминаторами и т. Д.

Что касается конкретного вопроса, который вы задали, я бы просто написал:

SetString(Result, M.Memory, M.Size div SizeOf(Result[1]) - 1);
person David Heffernan    schedule 19.01.2011
comment
Я избегаю модуля clipbrd, потому что мне нужно хранить и возвращать все доступные форматы, поэтому я имею дело только с API и точными буферами данных, которые Windows предоставляет в буфере обмена. Собственно, Clipboard.AsText делает кое-что изящное для решения моей проблемы: Результат: = PChar (GlobalLock (Data)). Поток с завершающим нулем соответствует формату PChar, но я бы не подумал, что получу копию таким образом. - person Marek Jedliński; 19.01.2011
comment
Если вы хотите избежать блока Clipboard, то я считаю, что вы нашли каноническое решение. Однако блок Clipboard можно легко использовать для чтения и записи нескольких форматов. Понятно, что вам не нужно его использовать, но я не вижу особых причин избегать этого. - person David Heffernan; 19.01.2011
comment
@Mood, Сохранение / восстановление буфера обмена возможно, но не без нежелательных побочных эффектов. Вы не сможете восстановить сложные форматы со 100% точностью, и вы не сможете манипулировать клипбордом, не вызывая проблем в других приложениях. См. Мой ответ на этот вопрос: stackoverflow.com/questions/4735559/ - person Chris Thornton; 19.01.2011
comment
@Chris: ваш собственный хорошо известный продукт (я зарегистрированный пользователь) очень хорошо справляется с тем, что, по вашему мнению, нельзя (или не следует) делать. Как такое могло быть? :) Я стремлюсь к чему-то намного, намного попроще - просто cf_text и cf_unicodetext. Я не касаюсь растровых изображений, объектов OLE или даже RTF. - person Marek Jedliński; 20.01.2011
comment
@mood -;) ну спасибо! Взгляните на мой профиль приложения в диалоговом окне параметров. Оттуда я позволяю пользователю выбирать форматы для захвата. Значения по умолчанию обычно представляют собой просто текст, HTML и растровое изображение. Он никогда не пытается получить все. И у меня там много логики повторных попыток, так как я ожидаю, что это не удастся. Однако большая разница в том, что я (обычно) не пытаюсь вернуть данные в буфер обмена сразу же после их поступления. т.е. я избегаю создания события буфера обмена ВО ВРЕМЯ события буфера обмена. - person Chris Thornton; 20.01.2011