delphi - 将 TStreamReader 与 TZDecompressionStream 结合使用时出现 EZDecompressionError

标签 delphi delphi-xe6

我有一个程序在 XE2 中运行良好,但现在在 XE6 中失败。追踪问题并不是太困难。我使用内置的 zip 处理类创建 TZDecompressionStream 并将其提交给 TStreamReader。该代码旨在支持普通的未压缩文件和压缩文件,因此读者会指向一个“FileStream”变量,该变量要么是 TFileStream(可以工作),要么是 TZDecompressionStream(显示错误)

Reader := TStreamReader.Create(FileStream, TEncoding.ASCII);

问题在于,TStreamReader 在读取数据时会调用内部例程 AdjustEndOfBuffer,该例程会尝试确保缓冲区始终只包含完整的字符。不幸的是,下面的行会在必要时倒带流,但无论是否需要倒带都会被调用

FStream.Position := FStream.Position - Rewind;

在我的例子中,Rewind 的值为零,而 TZDecompressionStream 对此异常(exception)。显然,这是 TZDecompressionStream 中的一个错误,因为对当前位置的查找应该被评估为 OK,因为它是无操作。实际的 Seek 在 TStreamReader 中表示为移动到相对于流开头的当前偏移量。

TZDecompressionStream.Seek 中的实际代码允许返回开始(即倒回流)、向前移动经过当前位置并移动到结束,但特别不允许移动精确给出当前位置。以下代码判断是否允许继续

(((NativeUInt(offset) - FZStream.total_out) > 0) and (Origin = soBeginning))

但应该有一个 >= 如下

(((NativeUInt(offset) - FZStream.total_out) >= 0) and (Origin = soBeginning))

有谁知道解决这个错误的方法而不是简单地放弃 TStreamReader 吗?有没有办法修改 TzipFile 类为我创建的 TZDecompressionStream 的行为?

最佳答案

我已经提出了自己的答案,可以避免弄乱 VCL 源代码。这不是一件很好的事情,但它相当简短,并且避免了系统其他部分的任何困惑。我已经实现了一个 TsptZDecompressionStream 类,它覆盖了错误的 Seek,并且基本上对移动为零进行了无操作。我有一个类函数 Convert,它用新类的 VMT 指针替换 TZDecompressionStream 中的 VMT 指针。这应该是安全的,因为除了重写的方法之外,这些类在所有方面都是相同的。遇到同样问题的其他人应该能够简单地使用此代码并调用

TsptZDecompressionStream.Convert(Stream as TZDecompressionStream);

触发转换

声明

type
  TsptZDecompressionStream = class(TZDecompressionStream)
    class procedure Convert(DecompressionStream: TZDecompressionStream);
    function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;
  end;

实现

{ TsptZDecompressionStream }

class procedure TsptZDecompressionStream.Convert(DecompressionStream: TZDecompressionStream);
begin
  // switch vmt pointer to point to TsptZDecompressionStream vmt
  PPointer(DecompressionStream)^ := PPointer(TsptZDecompressionStream);
end;

function TsptZDecompressionStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
begin
  if (Origin = soBeginning) and (Offset = Position) then
  begin
    Result := Offset;
    Exit;
  end
  else
  begin
    Result := inherited Seek(Offset, Origin);
  end;
end;

除了破解内部数据格式的常见问题之外,我想不出任何理由说明这是一个特别糟糕的主意,但我欢迎进一步的评论。

请注意,该实现适用于 64 位 Seek,因为这是 TZDecompressionStream 实现的版本。 Delphi 现在指示所有流应实现 32 位版本(旧的查找方法签名)或 64 位版本。请记住,我的代码将无法与实现 32 位 Seek 的 TZDecompressionStream 的任何版本一起运行(我不知道它的历史是否足够早,足以实现这种情况)

关于delphi - 将 TStreamReader 与 TZDecompressionStream 结合使用时出现 EZDecompressionError,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30176659/

相关文章:

Java 与 Delphi Base64 编码

delphi - 如何在Delphi中查找IHTMLDocument2是否等于IDispatch文档?

delphi - 编译器指令使字符串 <> UnicodeString

delphi - 检测 TWebBrowser 文档中的事件元素何时发生变化

delphi - 通过FTP上传文件片段

android - Firemonkey Android 获取电池电量

delphi - 什么是 Delphi/AppMethod BindSourceDB 范围映射以及如何使用它们?

delphi - 如何使用不太复杂的编码获取 MP3 文件的详细信息(如作者、标题、专辑、年份等)?

delphi - TQuery 上的 AfterScroll 和 AfterOpen

ios - 如何检查应用程序是否在 iOS 设备或模拟器上运行