我有一个函数可以创建指向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;
如何将其转换回来,从指针转换为动态字节数组 然后将内容保存到流中。或者也许可以直接从中加载流 指针?
感谢您的帮助。
答案 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 byte
或TStream
答案是,没有办法改回来。指针就是指向某些随机数据的指针。字节数组比它包含的数据多。 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将指针(+长度)放回流中。您可能需要首先清除流,因为大多数流写操作会从当前流位置追加。
希望有所帮助