【问题标题】:Why Delphi TMemoryStream Read TBytes and Array Of Bytes is different?为什么 Delphi TMemoryStream Read TBytes 和 Array Of Bytes 不同?
【发布时间】:2018-04-17 06:36:48
【问题描述】:

我正在编写一个使用 Delphi 2010 对字节序列进行 Z85Encoding 的程序。所以我写了一个函数:

function Z85Encode(input, output: TStream): integer

并期望传入 Stream。接下来我写了一个重载函数

function Z85Encode(b: TBytes): string;

它将字节序列写入TBytesStream(与TMemoryStream相同),调用encode函数,然后将编码数据读取到Result字符串中。

问题是我发现TStream.Read 的行为与文档非常不同,我无法理解。你我可以用其他方式来完成这个功能,但我不太明白为什么?我可能想知道 Delphi 是如何实现 TBytes 类型的。

为了说明我的问题,我编写了以下测试程序:

procedure Test;
var
  iStream: TMemoryStream;
  n: integer;
  a: TBytes;
  b: TBytes
  c: array [0..4] of Byte;
begin
  b := TEncoding.UTF8.GetBytes('abcdefghij');  // byte from 97 to 106
  iStream := TMemoryStream.Create;
  try
    iStream.Write(b, Length(b));
    iStream.Seek(0, soBeginning);
    n := iStream.Read(a, 5); // expect only 5 bytes read, but the whole array is back.
    ShowMessage(IntToStr(n));  // n is 5
    ShowMessage(IntToStr(Length(a)));  // length is 10!!
    iStream.Seek(0, soBeginning);
    n := iStream.Read(c, 5);   // c contains random number, not 97, 98, ...
    ShowMessage(IntToStr(n));  // n is 5
  finally
    iStream.Free;
  end;
end;

第一个问题,根据文档,Read 应该只读取字节数。但是如果我传入一个 TBytes 变量,它会读取整个字节数组,但返回的读取计数是 5。我也想知道为什么我不需要首先为变量 a 分配内存。 Stream.Read 似乎会为变量分配内存,这是意料之中的。

第二个问题,当TStream.Read(c, 5) 其中carray [0..4] of bytec 数组中的值是某个随机值,而不是 979899100101

通过进一步测试,如果我将 TStream.Write 更改为

for n := 0 to High(b) do begin
  iStream.Write(b[n], 1);
end;

那么Read(a, 5)不会得到任何结果,Length(a)会得到访问冲突,而Read(c, 5)会得到正确的结果。

似乎 ReadWrite 都采用开放类型参数,并且根据参数类型表现不同。如果最终要读取或写入变量的字节值,则表现不同是可以理解的,但它似乎做得更多,而不仅仅是从变量中确定字节内容。那么 ReadWrite 应该使用相同的变量类型,这不是我的预期。

【问题讨论】:

  • 至少在 Delphi2010 中 iStream.Write(b, Length(b));会造成麻烦。而是使用 iStream.Write(b[0], Length(b)); .原因是如果你写 b,则写入(隐藏的)动态数组记录中的数据而不是数据。
  • @mrabat 请注意,在 XE3 及更高版本中,Write() 的重载将 TBytes 作为输入。
  • 我明白了。猜猜我是时候更新到更新版本了;)
  • @mrabat 不是真的。许多新的流重载实际上令人困惑,并且最初使用毫无意义的堆分配非常糟糕地实现。

标签: delphi delphi-2010


【解决方案1】:

你没有写数组的内容。您正在编写数组的地址。动态数组是一个指针。那个指针的值就是你写的。

要写数组,你必须这样做

iStream.Write(b[0], Length(b));

或者

iStream.Write(Pointer(b)^, Length(b));

我更喜欢后者,因为即使启用了范围检查,它也适用于长度为 0 的数组。

同样从数组中读取。请注意,首先分配读取缓冲区是您的责任。你没有那样做。

SetLength(a, 5);
iStream.Read(Pointer(a)^, Length(a));

最后,使用WriteBufferReadBuffer 可能更可取,因为它们包含了错误检查,即请求的字节数已实际传输。

【讨论】:

  • 请注意,您所说的对于 Delphi 2010 来说是正确的。在 Delphi XE3 及更高版本中,Read/Buffer()Write/Buffer() 的重载可以处理 TBytes 而无需手动取消引用。跨度>
  • 我认为问题不在于写入,而在于读取。我尝试在写入后读取流,它仅包含字节内容,流大小为 10。即 iStream.Write(b, Length(b)) 和 iStream(b[0], Length(b)) 给出iStream 中的结果相同。我会仔细检查。
  • 不,Read 很好
猜你喜欢
  • 2017-08-26
  • 1970-01-01
  • 2014-01-25
  • 2021-02-18
  • 1970-01-01
  • 1970-01-01
  • 2016-06-02
  • 1970-01-01
  • 2019-07-15
相关资源
最近更新 更多