简体   繁体   中英

Delphi ZLib Compress / Decompress

I've got a minor issue with decompressing using the ZLib unit in Delphi

unit uZCompression;

interface

uses
  uCompression;

type
  TZZipCompression = class(TInterfacedObject, ICompression)
  public
    function DoCompression(aContent: TArray<Byte>): TArray<Byte>;
    function DoDecompression(aContent: TArray<Byte>): TArray<Byte>;
    function GetWindowsBits: Integer; virtual;
  end;

  TZGZipCompression = class(TZZipCompression)
    function GetWindowsBits: Integer; override;
  end;

implementation

uses
  System.ZLib, System.Classes, uMxKxUtils;

{ TZCompression }

function TZZipCompression.DoCompression(aContent: TArray<Byte>): TArray<Byte>;
var
  LContentStream, LOutputStream: TMemoryStream;
  LCompressedStream: TZCompressionStream;
begin
  LContentStream := ByteArrayToStream(aContent);

  LOutputStream := TMemoryStream.Create;

  LCompressedStream := TZCompressionStream.Create(LOutputStream, zcDefault, GetWindowsBits);
  LCompressedStream.CopyFrom(LContentStream, LContentStream.Size);
  LCompressedStream.Free;

  Result := StreamToByteArray(LOutputStream);

  LOutputStream.Free;
  LContentStream.Free;
end;

function TZZipCompression.DoDecompression(aContent: TArray<Byte>): TArray<Byte>;
var
  LContentStream, LOutputStream: TMemoryStream;
  LDecompressedStream: TZDecompressionStream;
begin
  LContentStream := ByteArrayToStream(aContent);

  LOutputStream := TMemoryStream.Create;

  LDecompressedStream := TZDecompressionStream.Create(LContentStream);
  LOutputStream.CopyFrom(LDecompressedStream, LDecompressedStream.Size);
  LDecompressedStream.Free;

  Result := StreamToByteArray(LOutputStream);

  LOutputStream.Free;
  LContentStream.Free;
end;

function TZZipCompression.GetWindowsBits: Integer;
begin
  Result := 15;
end;

{ TZGZipCompression }

function TZGZipCompression.GetWindowsBits: Integer;
begin
  Result := inherited;
  Result := Result + 16;
end;

end.

This is my unit which is driven by an interface (which you don't need to know about), and data is passed in and out through a TArray variables.

I've coded it to be able to do 2 types of compression, standard zip and gzip which is determined by the windowsbits passed in to the functions.

Here are a couple of other functions being used to convert the TArray to TMemoryStream

function ByteArrayToStream(aContent: TArray<Byte>): TMemoryStream;
begin
  Result := TMemoryStream.Create;
  Result.Write(aContent, length(aContent)*SizeOf(aContent[0]));
  Result.Position := 0;
end;

function StreamToByteArray(aStream: TMemoryStream): TArray<Byte>;
var
  LStreamPos: Int64;
begin
  if Assigned(aStream) then
  begin
    LStreamPos := aStream.Position;
    aStream.Position := 0;
    SetLength(Result, aStream.Size);
    aStream.Read(Result, aStream.Size);
    aStream.Position := LStreamPos;
  end
  else
    SetLength(Result, 0);
end;

Now I can compress and decompress to .zip using the TZZipCompression class perfectly fine (it doesn't open up as a zip file, but it does decompress back to the original file which I can open and edit).

I can also compress to .gz using the TZGZipCompression class fine as well (interestingly I can open this gzip file perfectly well).

My issue however is that it won't decompress back from the .gz file and throws and error as soon as it hits

LOutputStream.CopyFrom(LDecompressedStream, LDecompressedStream.Size)

Funnily enough the Help file example has it as below

LOutputStream.CopyFrom(LDecompressedStream, 0)

But this doesn't work either.

Can anyone spot the issue?

Your conversion functions between TArray<Byte> and TMemoryStream are wrong, as you are not accessing the array content correctly. TArray is a dynamic array. When calling TMemoryStream.Write() and TMemoryStream.Read() , you are passing the memory address of the TArray itself, not the memory address of the data that the TArray points at. You need to reference the TArray to get the correct memory address, eg:

function ByteArrayToStream(const aContent: TArray<Byte>): TMemoryStream;
begin
  Result := TMemoryStream.Create;
  try
    if Length(aContent) > 0 then
      Result.WriteBuffer(aContent[0], Length(aContent));
    Result.Position := 0;
  except
    Result.Free;
    raise;
  end;
end;

function StreamToByteArray(aStream: TMemoryStream): TArray<Byte>;
begin
  if Assigned(aStream) then
  begin
    SetLength(Result, aStream.Size);
    if Length(Result) > 0 then
      Move(aStream.Memory^, Result[0], aStream.Size);
  end
  else
    SetLength(Result, 0);
end;

Alternatively:

function ByteArrayToStream(const aContent: TArray<Byte>): TMemoryStream;
begin
  Result := TMemoryStream.Create;
  try
    Result.WriteBuffer(PByte(aContent)^, Length(aContent));
    Result.Position := 0;
  except
    Result.Free;
    raise;
  end;
end;

function StreamToByteArray(aStream: TMemoryStream): TArray<Byte>;
begin
  if Assigned(aStream) then
  begin
    SetLength(Result, aStream.Size);
    Move(aStream.Memory^, PByte(Result)^, aStream.Size);
  end
  else
    SetLength(Result, 0);
end;

That being said, you don't need to waste memory making copies of the array data using TMemoryStream . You can use TBytesStream instead (since dynamic arrays are reference counted), eg:

function TZZipCompression.DoCompression(aContent: TArray<Byte>): TArray<Byte>;
var
  LContentStream, LOutputStream: TBytesStream;
  LCompressedStream: TZCompressionStream;
begin
  LContentStream := TBytesStream.Create(aContent);
  try
    LOutputStream := TBytesStream.Create(nil);
    try    
      LCompressedStream := TZCompressionStream.Create(LOutputStream, zcDefault, GetWindowsBits);
      try
        LCompressedStream.CopyFrom(LContentStream, 0);
      finally
        LCompressedStream.Free;
      end;
      Result := Copy(LOutputStream.Bytes, 0, LOutputStream.Size);
    finally
      LOutputStream.Free;
    end;
  finally
    LContentStream.Free;
  end;
end;

function TZZipCompression.DoDecompression(aContent: TArray<Byte>): TArray<Byte>;
var
  LContentStream, LOutputStream: TBytesStream;
  LDecompressedStream: TZDecompressionStream;
begin
  LContentStream := TBytesStream.Create(aContent);
  try    
    LOutputStream := TBytesStream.Create(nil);
    try
      LDecompressedStream := TZDecompressionStream.Create(LContentStream, GetWindowsBits);
      try
        LOutputStream.CopyFrom(LDecompressedStream, 0);
      finally
        LDecompressedStream.Free;
      end;
      Result := Copy(LOutputStream.Bytes, 0, LOutputStream.Size);
    finally
      LOutputStream.Free;
    end;
  finally
    LContentStream.Free;
  end;
end;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM