Как преобразовать указатель обратно в массив байтов (или поток)? - PullRequest
2 голосов
/ 16 февраля 2012

У меня есть функция, которая создает указатель на данные из потока.

function StreamToByteArray(Stream: TStream): Pointer;
var
  ByteArr: array of Byte;
begin
  if Assigned(Stream) then
  begin
    Stream.Position := 0;
    SetLength(ByteArr, Stream.Size);
    Stream.Read(ByteArr[0], Stream.Size);
  end
  else
    SetLength(ByteArr, 0);
  result := @ByteArr[0];
end;

Как преобразовать его обратно из указателя в динамический байтовый массив и затем сохранить содержимое в поток.Или, может быть, можно загрузить поток напрямую из указателя?

Спасибо за помощь.

Ответы [ 4 ]

12 голосов
/ 16 февраля 2012

Ой, этот код (к сожалению) очень плохой. Ваша функция возвращает указатель на массив ByteArr, но, к сожалению, этот массив выходит из области видимости, когда функция существует: вы по сути возвращаете Invalid Pointer! Даже если ошибка не появляется сразу же, у вас есть скрытое нарушение прав доступа.

Более длинное объяснение

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

Концепция Pointer тесно связана с концепцией выделения памяти. Мы используем много разных техник для выделения памяти, используя локальные переменные, глобальные переменные, объекты, динамические массивы и т. Д. В вашей функции-примере вы используете динамический массив array of Byte. Компилятор очень хорошо защищает вас от внутренней памяти выделения и перераспределения памяти, вы можете просто использовать SetLength(), чтобы сказать, насколько большим должен быть массив. Все работает очень хорошо, потому что динамический массив представляет собой управляемую структуру данных в Delphi: компилятор отслеживает, как вы используете динамический массив, и освобождает связанную память, как только динамический массив больше не нужен. Что касается компилятора, связанная память больше не требуется, когда ваша функция существует.

Когда вы делаете:

Result := @ByteArr[0];

По сути, вы берете адрес для выделенного компилятором блока памяти. Поскольку для этого используется структура очень низкого уровня (Pointer), компилятор не может отслеживать использование вами памяти, поэтому он освободит память, когда функция будет существовать. Это оставляет вас с указателем на нераспределенную память.

Как правильно вернуть Pointer из функции

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

Тем не менее, если вы действительно хотите вернуть указатель, вы должны вернуть указатель на явно выделенную память, чтобы вы знали, что компилятор не освобождает его для вас. Когда вы сделаете это, убедитесь, что принимающий код знает, что он отвечает за память (следует освободить память, когда она больше не нужна). Например, ваша функция может быть переписана так:

function StreamToByteArray(Stream: TStream): Pointer;
begin
  if Assigned(Stream) then
    begin
      Result := AllocMem(Stream.Size);
      Stream.Position := 0;
      Stream.Read(Result^, Stream.Size);
    end
  else
    Result := nil;
end;

Как изменить указатель обратно на array of byte или TStream

Ответ - нет способа вернуть его обратно. Указатель - это просто указатель на случайные данные. Массив байтов больше, чем данные, которые он содержит. TStream является еще более абстрактным: это интерфейс, который говорит вам, как получить данные, он не обязательно содержит какие-либо данные. Например, TFileStreamравен a TStream) не содержит любых байтов данных: все данные находятся в файле на диске.

3 голосов
/ 16 февраля 2012

Возможное решение:

type
  TBytes = array of byte;

function StreamToByteArray(Stream: TStream): TBytes;
begin
  if Assigned(Stream) then
  begin
    Stream.Position := 0;
    SetLength(result, Stream.Size);
    Stream.Read(pointer(result)^, Stream.Size);
  end
  else
    SetLength(result, 0);
end;

procedure Test;
var P: pointer;
begin
  P := pointer(StreamToByteArray(aStream)); // returns an allocated TBytes
  // ... use P
end; // here the hidden TBytes will be released

Вы можете использовать pointer() вокруг результата, чтобы получить место в памяти.

И ваш код не утечет память и не вызовет нарушения прав доступа, поскольку неявный блок try ... finally будет добавлен компилятором:

procedure Test;
var P: pointer;
    tmp: TBytes; // created by the compiler
begin
  tmp := StreamToByteArray(aStream)); // returns an allocated TBytes
  try
    P := pointer(tmp);
    // ... use P
  finally  // here the hidden TBytes will be released
    Finalize(tmp);
  end;
end; 

Вы можете использовать RawByteString вместо TBytes, если хотите.

2 голосов
/ 16 февраля 2012

Если вам нужен указатель на память, чтобы перейти к, например. Функция в DLL, вы должны сделать этот вызов, пока буфер еще выделен. Существует множество способов рефакторинга приведенного ниже кода, но один и тот же принцип применяется независимо от того, как заканчивается ваш код: вы не должны передавать указатель после того, как буфер уже освобожден.

var
  ByteArr: array of Byte;
begin
  if Assigned(Stream) then
  begin
    Stream.Position := 0;
    SetLength(ByteArr, Stream.Size);
    Stream.Read(ByteArr[0], Stream.Size);
  end
  else
    SetLength(ByteArr, 0);
  Test(Pointer(ByteArray),Length(ByteArray));
end;

В вашей процедуре тестирования вы можете сделать это:

procedure Test(aData: Pointer; aCount: Integer);
var
  ByteArr: array of Byte;
begin
  SetLength(ByteArr,aCount);
  Move(aData^,Pointer(ByteArr)^,aCount);
0 голосов
/ 16 февраля 2012

Cosmin прав: вы получаете указатель на массив, который выйдет из области видимости, указатель укажет на область памяти, находящуюся в стеке, и может быть перезаписан. Может показаться, что функция работает, если вынемедленно используйте resust.

Вам также нужно передать массив для заполнения в функцию, или, как я обычно делаю (в зависимости от типа данных), просто вернуть строку и использовать ее в качестве байтового массива (если вы намереваетесь перейти на более новый Delphi, вам нужно быть осторожным, какую строку вы используете.)

Также динамические массивы хранят длину и тип данных перед данными (8 байт) и передают указатели на первыйэлемент теряет тот факт, что он является динамическим массивом и становится просто буфером памяти, что делает освобождение массива опасным.

Чтобы ответить на ваш вопрос, указатель (+ длина) может быть возвращен в поток с помощью TStream.WriteBuffer.,Вам может понадобиться сначала очистить поток, так как это, как и большинство операций записи потока, будет добавлено из текущей позиции потока.

Надеюсь, это поможет

...