Delphi 使用TStreamReader和TZDecompressionStream的EZDecompressionError

Delphi 使用TStreamReader和TZDecompressionStream的EZDecompressionError,delphi,delphi-xe6,Delphi,Delphi Xe6,我有一个程序在XE2中运行良好,但现在在XE6中失败。追踪问题并不太困难。我使用内置的zip处理类创建TZDecompressionStream,并将其提交给TStreamReader。代码旨在支持普通的未压缩文件和压缩文件,因此读取器指向一个“FileStream”变量,该变量是TFileStream(有效)或TZDecompressionStream(显示故障) 问题是TStreamReader在读取数据时调用内部例程AdjustEndOfBuffer,该例程尝试确保缓冲区始终只包含完整字符

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

问题是TStreamReader在读取数据时调用内部例程AdjustEndOfBuffer,该例程尝试确保缓冲区始终只包含完整字符。不幸的是,这有下面一行,它在必要时回放流,但在需要回放或不需要回放时调用

FStream.Position := FStream.Position - Rewind;
在我的例子中,Rewind的值为零,TZDecompressionStream对此表示异议。显然,这是TZDecompressionStream中的一个错误,因为对当前位置的寻道应被评估为OK,因为它是no-op。实际寻道在TStreamReader中表示为相对于流开始的移动到当前偏移

TZDecompressionStream.Seek中的实际代码允许返回开始(即倒带流)、向前移动超过当前位置并移动到结束,但不允许准确给出当前位置的移动。以下代码决定是否允许继续前进

(((NativeUInt(offset) - FZStream.total_out) > 0) and (Origin = soBeginning))
但是应该有一个>=如下所示

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

有没有人知道一种不放弃TStreamReader就可以绕过这个错误的方法?有没有办法修改TzipFile类为my创建的TZDecompressionStream的行为?

我已经找到了自己的答案,避免了与VCL源代码的混淆。这不是一件很好的事情,但它相当简单,避免了系统其他部分的混淆。我已经实现了一个类TsptZDecompressionStream,它覆盖了错误的Seek,并且基本上没有操作,移动为零。我有一个类函数Convert,它将TZDecompressionStream中的VMT指针替换为我的新类的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位版本(旧seek方法签名)或64位版本。请记住,我的代码不会与任何实现32位Seek的TZDecompressionStream版本结合使用(我不知道它是否可以追溯到足够远的时候,以至于这种情况曾经发生过)

稍微优雅一点?解决方案是使用所谓的拦截器类,本质上是从原始类派生的与原始类同名的类。我经常使用这些工具,效果很好

该方法在网络上的一些地方进行了描述,例如这里

你必须对uses子句的顺序稍微小心一点,但除此之外,它们工作得很好

除了命名和不再需要转换之外,编码几乎完全符合您的示例

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

我一点也不清楚你在干什么。我不明白文本编码在哪里变成了解压代码。如果有mcve,它可能会很清楚。@DavidHeffernan:
TStreamReader
从底层
TStream
读取字节,并使用指定的
TEncoding
将其转换为文本。所讨论的输入文件是压缩的,因此使用
TZDecompressionStream
作为
TStream
,因此字节在转换为文本之前被解压缩。@Remy如果FileStream是一个解压缩文件,这是有意义的stream@DavidHeffernan:“我创建了一个TZDecompressionStream…并将其提交给TStreamReader”,我认为这意味着
FileStream
是一个
TZDecompressionStream
@DavidHeffernan我试图澄清这个立场。问题是我已经诊断出Delphi Zip库中的故障,但我不确定解决它的最佳方法(如果有的话)。我真的想用TZDecompressionStream的子类稍微修改一下Seek,但是因为我没有创建对象,所以我不知道如何管理它。这看起来比你需要的复杂得多。为什么要搞VMT?为什么不实例化派生类呢?实际上,按照我在最后的评论中的建议去做。@DavidHeffernan RTLzlib包装器的工作方式意味着您无法控制流的类类型。Read方法有一个out参数,用于返回库中生成的流对象。我可以包装这个流,但这意味着连接了很多方法,这是相当多的锅炉板代码。我同意破解VMT是一个相当不舒服的步骤,但它至少是简洁的。我懂了。在这种情况下,我想我会用一个修改过的版本替换zlib.pas。这将是我个人的选择,但我同意这确实取决于个人偏好。我现在必须替换zlib.pas来修复x64下zlib的性能,所以我对这种方法很满意。在对他的回答的评论中,提问者解释说框架实例化了类。因此子类化将不起作用。
type
  TZDecompressionStream= class(TZDecompressionStream)
    function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;
  end;