使用TSTreamReader和TZDecompressionStream的EZDecompressionError

时间:2015-05-11 20:03:37

标签: 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对此异常。显然,这是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的行为?

2 个答案:

答案 0 :(得分:2)

我提出了自己的答案,避免搞乱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位版本(旧搜索方法签名)或64位版本。请记住,我的代码不能与任何实现32位Seek的TZDecompressionStream版本一起使用(我不知道它是否可以追溯到远远不足以达到这种情况)

答案 1 :(得分:0)

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

该方法在网上的几个地方描述,例如这里

interceptor classes

你必须对使用条款的顺序有点小心但除此之外它们工作正常。

编码几乎与您的示例完全相同,除了命名和不再需要转换的事实。

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