如何将指针转换回字节数组(或流)?

时间:2012-02-16 10:14:04

标签: delphi delphi-7

我有一个函数可以创建指向Stream数据的指针。

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 个答案:

答案 0 :(得分:12)

哎呀,这个代码(不幸的是)非常糟糕。你的函数返回一个指向ByteArr数组的指针,但不幸的是,当函数存在时,该数组超出了范围:你实际上是在返回一个无效的指针!即使错误没有立即弹出,你也会遇到潜在的访问冲突。

更长的解释

Pointer是一种危险的结构:它不包含数据,只是说明数据存在的位置。你的无类型Pointer的例子是最难的一种指针,它说 nothing 关于给定地址存在的数据。它可能指向您从流中读取的某些字节,可能指向字符串甚至某些类型的图片。您甚至无法知道给定地址的数据量。

指针概念与分配内存的概念密切相关。我们使用许多不同的技术来分配内存,使用局部变量,全局变量,对象,动态数组等。在您的示例函数中,您使用的是动态数组array of Byte。编译器可以很好地屏蔽你分配和重新分配内存的内部,你可以简单地使用SetLength()来说明数组应该有多大。事情很好,因为动态数组是Delphi中的托管数据结构:编译器跟踪你如何使用动态数组,并将释放相关内存一旦不再需要动态数组。就编译器而言,当函数存在时,不再需要相关的内存。

当你在做的时候:

Result := @ByteArr[0];

您实际上是在为编译器分配的内存块获取地址。由于您使用非常低级别的结构(Pointer),编译器无法跟踪您对内存的使用情况,因此当函数存在时它将释放内存。这会留下指向未分配内存的指针。

如何从函数

正确返回Pointer

首先,如果可能的话,你应该避免使用Pointers:它们是低级的,编译器无法帮助进行类型安全或释放,它们很容易出错。当你确实指出错误时,错误通常是访问冲突,而且很难跟踪。

那就是说,如果确实想要返回一个指针,你应该返回一个显式分配内存的指针,所以你知道编译器不会为你释放它。当你这样做时,确保接收代码知道它负责内存(应该在不再需要内存时释放内存)。例如,您的函数可以像这样重写:

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 byteTStream

答案是,没有办法改回来。指针就是指向某些随机数据的指针。字节数组比它包含的数据多。 TStream更加抽象:它是一个告诉您如何检索数据的界面,它不一定包含任何数据。例如,TFileStream a TStream)不包含任何字节的数据:所有数据都在文件中在磁盘上。

答案 1 :(得分:3)

可能的解决方案:

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 :(得分:2)

如果你需要一个指向内存的指针来传递到例如在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);

答案 3 :(得分:0)

Cosmin是正确的你正在退回指向一个超出范围的数组的指针,指针将指向堆栈中的内存区域并可能被覆盖,看起来好像该函数可以正常工作立即使用救援。

你需要将要填充的数组传递给函数,或者像我通常那样(根据数据类型)简单地返回一个字符串并将其用作字节数组(如果你打算转移到更新的字符串中) Delphi你需要小心使用哪种字符串类型。

此外,动态数组在数据(8个字节)之前存储长度和数据类型,并且将指针传递给第1个元素会使其成为动态数组,并且只会成为一个内存缓冲区,从而使阵列的释放变得危险。

要回答您的问题,可以使用TStream.WriteBuffer将指针(+长度)放回流中。您可能需要首先清除流,因为大多数流写操作会从当前流位置追加。

希望有所帮助