Crear un archivo gzip con Delphi

El siguiente código muestra como crear un archivo comprimido gzip (.gz) usando solo las funciones que aparecen en la unit zlib que viene con Delphi, sin necesidad de utilizar dlls externas. Al utilizar este formato podremos abrir los datos comprimidos con otros programas como gzip, 7zip, winrar, etc ... pudiendo así generar ficheros que podrán ser abiertos por otras personas, sin necesidad de que tengan instalada nuestra aplicación.

El archivo se crea siguiendo el formato que se describe en este documento RFC 1952 GZIP File Format Specification version 4.3. Como se puede leer en el texto anterior, el formato gzip es bastante sencillo, tiene una cabecera, los datos comprimidos y un "pie" y solo permite comprimir un archivo de cada vez, por eso normalmente se suele combinar con otros formatos como "tar" para poder comprimir mas de un fichero dentro del mismo archivo comprimido.

La siguiente "unit" exporta una función (MakeGZ) que crea un fichero .gz a partir de un Stream de entrada y lo guarda en un Stream de salida, permitiendo así comprimir un fichero, texto, datos en memoria o cualquier otra cosa que pueda ser manejado como un Stream, pudiendo guardar el fichero resultante en disco o en memoria.

unit gzip;
 
interface
 
uses Sysutils, Windows, Classes;
 
procedure MakeGZ(Src, Dst: TStream);
 
implementation
 
uses zlib;
 
type
  {
    +---+---+---+---+---+---+---+---+---+---+
    |ID1|ID2|CM |FLG|     MTIME     |XFL|OS |
    +---+---+---+---+---+---+---+---+---+---+
  }
  TGZHeader = packed record
    ID1: Byte;          // IDentification 1 = 31
    ID2: Byte;          // IDentification 2 = 139
    CM: Byte;           // Compression Method = 8
    FLG: Byte;          // Flags
    MTIME: Cardinal;    // Modification TIME
    XFL: Byte;          // Extra Flags
    OS: Byte;           // Operating System
  end;
 
  {
    +---+---+---+---+---+---+---+---+
    |     CRC32     |     ISIZE     |
    +---+---+---+---+---+---+---+---+
  }
 
  TGZFooter = packed record
    CRC32: Cardinal;    // CRC-32
    ISIZE: Cardinal;    // Input SIZE
  end;
 
var
  CRCTable: array[0..255] of Cardinal;
 
procedure MakeCRCTable;
var
  c: Cardinal;
  n,k: Integer;
begin
  for n:= 0 to 255  do
  begin
    c:= n;
    for k:= 0 to 7 do
      if Odd(c) then
        c:= $edb88320 xor (c shr 1)
      else
        c:= c shr 1;
    CRCTable[n]:= c;
  end;
end;
 
function CCheck(code: Integer): Integer;
begin
  Result := code;
  if code < 0 then
    raise ECompressionError.Create('Error al comprimir');
end;
 
// Calcula el CRC-32
function UpdateCRC(CRC: Cardinal; Buffer: PByte; Len: Integer): Cardinal;
begin
  CRC:= CRC xor $ffffffff;
  while Len > 0 do
  begin
    CRC:= CRCTable[(CRC xor Cardinal(Buffer^)) and $ff] xor (CRC shr 8);
    dec(Len);
    inc(Buffer);
  end;
  Result:= CRC xor $ffffffff;
end;
 
const
  BUFFERSIZE = High(Word);
 
procedure MakeGZ(Src, Dst: TStream);
var
  GZHeader: TGZHeader;
  GZFooter: TGZFooter;
  Zrec: TZStreamRec;
  TmpStream: TMemoryStream;
  inBuffer,outBuffer: PChar;
  n: Integer;
 
  // Utilizamos este buffer temporal para eliminar la cabecera y el pie de zlib
  procedure WriteTempBuffer(Buffer: PChar; Count: Longint);
  begin
    // Si es la primera escritura
    if TmpStream.Size = 0 then
    begin
      // Eliminamos la cabecera
      inc(Buffer,2);
      dec(count,2); 
    end else
      Dst.WriteBuffer(TmpStream.Memory^,TmpStream.Size);
    TmpStream.Clear;
    TmpStream.WriteBuffer(Buffer^,Count);
  end;  
 
begin
  // Inicializamos la cabecera
  with GZHeader do
  begin
    ID1:= 31;
    ID2:= 139;
    CM:= 8;
    FLG:= 0; 
    MTIME:= 0;
    XFL:= 0;
    OS:= 255;
  end;
  // Incializamos el pie
  GZFooter.CRC32:= 0;
  GZFooter.ISIZE:= 0;
  // Creamos un buffer temporal
  TmpStream:= TMemoryStream.Create;
  try
    // Creamos el buffer de entrada
    GetMem(inBuffer,BUFFERSIZE);
    try
      // Creamos el buffer de salida
      GetMem(outBuffer,BUFFERSIZE);
      try
        // Escribimos la cabecera del archivo gz al stream de salida
        Dst.WriteBuffer(GZHeader,Sizeof(GZHeader));
        FillChar(ZRec,Sizeof(ZRec),#0);
        ZRec.zalloc:= zlibAllocMem;
        ZRec.zfree:= zlibFreeMem;
        ZRec.next_out:= outBuffer;
        ZRec.avail_out:= BUFFERSIZE;
        // Inicializamos zlib
        CCheck(deflateInit_(ZRec, Z_DEFAULT_COMPRESSION, zlib_version, sizeof(ZRec)));
        repeat
          n:= Src.Read(inBuffer^,BUFFERSIZE);
          // Ajustamos los valores del pie
          inc(GZFooter.ISIZE,n);
          GZFooter.CRC32:= UpdateCRC(GZFooter.CRC32,PByte(inBuffer),n);
          ZRec.next_in:= inBuffer;
          ZRec.avail_in:= n;
          while (ZRec.avail_in > 0) do
          begin
            CCheck(deflate(ZRec, 0));
            if ZRec.avail_out = 0 then
            begin
              WriteTempBuffer(outBuffer, BUFFERSIZE);
              ZRec.next_out:= outBuffer;
              ZRec.avail_out:= BUFFERSIZE;
            end;
          end;
        until n < BUFFERSIZE;
        ZRec.next_in:= nil;
        ZRec.avail_in:= 0;
        try
          while (CCheck(deflate(ZRec, Z_FINISH)) <> Z_STREAM_END)
            and (ZRec.avail_out = 0) do
          begin
            WriteTempBuffer(outBuffer, BUFFERSIZE);
            ZRec.next_out:= outBuffer;
            ZRec.avail_out := BUFFERSIZE;
          end;
          if ZRec.avail_out < BUFFERSIZE then
            WriteTempBuffer(outBuffer, BUFFERSIZE - ZRec.avail_out);
        finally
          deflateEnd(ZRec);
        end;
        // Escribimos los bytes que quedan en el pie menos los 4 bytes que añade zlib
        Dst.WriteBuffer(TmpStream.Memory^,TmpStream.Size-4);
        // Escribimos el pie
        Dst.WriteBuffer(GZFooter,Sizeof(GZFooter));
      finally
        FreeMem(outBuffer);
      end;
    finally
      FreeMem(inBuffer);
    end;
  finally
    TmpStream.Free;
  end;
end;
 
 
initialization
  MakeCRCTable; 
end.

Un ejemplo de como usarla:

var
  Src, Dst: TFileStream;
begin
  Src:= TFileStream.Create('C:\Origen.txt',fmOpenRead);
  try
    Dst:= TFileStream.Create('C:\Origen.txt.gz',fmCreate);
    try
      MakeGZ(Src,Dst);
    finally
      Dst.Free;
    end;
  finally
    Src.Free;
  end;
end;

Enlaces de interés:
http://www.gzip.org/zlib/rfc-gzip.html

Comentarios

Buenas compañero

La verdad es que hace años que uso la librería zlib para hacer archivos comprimidos y no sabía que se podía crear archivos gz con ella y que, como bien dices, hace compatible este formato de compresión con los descompresores más estándares del mercado.

De hecho, hasta me llegué a hacer un pequeño des/compresor de archivos zlib, muy sencillo y rústico, pero que me servía para ver el contenido de los archivos generados por mi aplicación y poder descomprimirlos de forma rápida.

En fin, que no me enrollo más, que muchas gracias por compartir este código con todos.

Nos leemos
cadetill

Gracias Cadetill,

Si te interesa el tema no te pierdas mi ultimo post, donde explico como hacer un archivo tar.gz, para así poder añadir mas de un fichero, e incluso toda la estructura de directorios, a un fichero comprimido

http://delphi.jmrds.com/node/63

Nos leemos

Hola Seoane.

una duda enorme. como se puede agregar un progressbar al usar zlib.pas. Con el código uno comprime y solo espera a que termine, pero esta muy de moda el poder ver el progreso de la compresión.

Saludos..

En ese caso ya tendrías que integrarlo todo dentro de tu aplicación poniendo un progressbar en el formulario y actualizándolo desde el la función MakeGZ.

Concretamente dentro del bucle

repeat
     ...   
until n < BUFFERSIZE;

Ahí tienes que introducir una instrucción para incrementar el progressbar, y una llamada a Application.Processmessage para que se actualice.

Saludos

Me podrían ayudar por favor necesito crear el gzip pero poder seleccionar el archivo que quiero comprimir como lo puedo hacer gracias

¿A que te refieres con "seleccionarlo"?.

En el código se puede ver claramente donde indicamos que archivo queremos comprimir:

Src:= TFileStream.Create('C:\Origen.txt',fmOpenRead);

he estado trantando de usar la Unit pero me da error en esta asignación
ZRec.next_out:= outBuffer;
me dice que son imconpatibles los tipos char y ansichar

Cambiando

inBuffer,outBuffer: PChar;

Por esto, debería funcionar:

inBuffer,outBuffer: PAnsiChar;

En las versiones anteriores de Delphi PChar y PAnsiChar eran equivalente, en las nuevas ya no.

Saludos