Acceder a ficheros comprimidos desde C++

Comprimir y descomprimir

Hace unos meses me encontré con la necesidad de acceder a ciertos ficheros desde una aplicación C++.

Estos ficheros individuales están contenidos dentro de un fichero comprimido con la extensión ZIP.

Así que me puse a buscar alguna librería C o C++ que permitiera acceder a ficheros comprimidos, y después de probar algunas, con resultados decepcionantes, di con una página que contiene unas utilidades que permiten hacerlo de una forma sencilla y rápida, dos de las características que considero más importantes y deseables en cualquier caso.

Así que lo comparto con vosotros, al tiempo que explico cómo usarlo y muestro algunos ejemplos.

Descargar el paquete

Lo primero es conseguir el "paquete". No se trata de una librería, sino de un conjunto de ficheros entre los que hay cuatro que son los que nos interesan. Dos ficheros C++ y dos ficheros de cabecera. Estos cuatro ficheros forman dos parejas, una para comprimir y otra para descomprimir, que tienen los nombres: (zip.cpp, zip.h) y (unzip.cpp, unzip.h).

Esto, de entrada, ya es una buena noticia, puesto que simplifica mucho la inclusión de ficheros en un proyecto que use ficheros comprimidos. En mi caso, por ejemplo, sólo necesité añadir al proyecto la pareja (unzip.cpp, unzip.h), dado que no necesitaba comprimir ficheros, sólo descomprimirlos.

El paquete se descarga desde esta página: Zip Utils - clean, elegant, simple, C++/win32

Como el propio título indica, es una forma limpia, elegante y simple de utilidades de compresión, para C++ y para el API 32 de Windows. El señor Lucian Wischik se ha tomado la molestia de reempaquetar el código de zlib disponible en www.gzip.org/zlib, escrito por Jean-Loup Gailly y Mark Adler entre otros.

El paquete contiene también otros ficheros: un readme.txt, un fichero HTML con el contenido de la página desde la que lo hemos descargado, y algunos ejemplos de uso.

Descomprimir el paquete

Ya sé que parece raro que una utilidad para descomprimir y comprimir ficheros se distribuya comprimida, pero el problema nunca fue comprimir o descomprimir ficheros, sino hacerlo desde nuestros programas C++.

De modo que sería conveniente guardar el fichero comprimido en un lugar seguro y accesible, y descomprimir los ficheros que necesitemos en la carpeta del proyecto que estemos desarrollando. A veces necesitaremos los cuatro ficheros, y otras veces sólo una de las parejas de ficheros.

Descomprimir ficheros

Veamos primero un ejemplo sencillo para descomprimir ficheros desde C++.

#include <iostream>
#include <windows.h>
#include "unzip.h"

using namespace std;

int main() {
    HZIP hz = OpenZip("fichero.zip", 0);
    ZIPENTRY ze;

    GetZipItem(hz, -1, &ze);
    int numitems=ze.index;

    for (int i = 0; i < numitems; i++) {
        GetZipItem(hz, i, &ze);
        cout << ze.name << endl;
        UnzipItem(hz, i, ze.name);
    }
    CloseZip(hz);
}

La idea de este pequeño programa es sencilla:

  1. Obtenemos un manipulador HZIP para el fichero zip, usando la función OpenZip. El primer parámetro es el nombre del fichero, el segundo, la contraseña, o 0 si no hay.
  2. Obtenemos un objeto ZIENTRY, con el índice -1 como segundo parámetro, usando la función GetZipItem. Este valor de índice obtiene un objeto que contiene características del fichero zip completo, como el número de ficheros que contiene. Para el primer parámetro indicamos el manipulador del fichero zip, y en el tercero, un puntero a la estructura ZIENTRY que recibirá los datos obtenidos.
  3. El dato index del objeto ZIENTRY obtenido contiene el número de ficheros que contiene el fichero comprimido. Almacenamos ese valor para usarlo en el bucle que recorre cada uno de los ficheros contenidos en el fichero zip.
  4. Volvemos a usar la función GetZipItem, esta vez con el índice de cada uno de los ficheros, empezando en 0, para obtener un objeto ZIENTRY con la descripción de cada fichero.
  5. En nuestro ejemplo mostramos el nombre del fichero, que está en el campo name, y lo extraemos descomprimido, usando la función UnzipItem, usando el mismo valor de índice, y el nombre que queremos que tenga el fichero, que será el mismo almacenado en name. Este nombre contiene la ruta relativa completa, por lo que se creará la estructura de directorios original.
  6. Una vez descomprimidos todos los ficheros, liberamos el manipulador usando la función CloseZip.

En el proyecto será necesario añadir los ficheros "unzip.cpp" y "unzip.h".

En el futuro crearé una referencia completa de las funciones y estructuras incluidas en esta librería.

Comprimir ficheros

Ahora veamos un ejemplo sencillo para comprimir ficheros desde C++.

#include <windows.h>
#include <iostream>
#include "zip.h"

using namespace std;

int main() {
    HZIP hz;

    hz = CreateZip("ejemplo2.zip",0);
    ZipAdd(hz,"main.cpp", "main.cpp");
    ZipAdd(hz,"zip.cpp", "zip.cpp");
    ZipAdd(hz,"zip.h", "zip.h");
    ZipAdd(hz,"bin/debug/zip.exe", "./bin/debug/zip.exe");
    CloseZip(hz);
    return 0;
}

Tampoco en este ejemplo hay nada complicado:

  1. Creamos el fichero zip y obtenemos un manipulador HZIP para él, usando la función CreateZip. El primer parámetro es el nombre del fichero. El segundo, la contraseña, o 0 si no hay contraseña.
  2. Añadimos ficheros usando la función ZipAdd. El primer parámetro es el manipulador del fichero zip, el segundo el nombre que tendrá el fichero dentro del zip, y el tercero, el nombre del fichero a añadir. En el caso del nombre dentro del zip, no se deben incluir los directorios . o .. como parte del nombre.
  3. Cerramos el fichero y el manipulador, usando la función CloseZip.

En el proyecto será necesario añadir los ficheros "zip.cpp" y "zip.h".

Combinar compresiones y descompresiones

Evidentemente, si nuestra aplicación necesita comprimir y descomprimir ficheros, podemos combinar ambas características. Sólo será necesario incluir en el proyecto los cuatro ficheros: "zip.h", "zip.cpp", "unzip.h" y "unzip.cpp".

Otras posibilidades

Tan sólo añadir a lo anterior que esta utilidad permite usar archivos comprimidos de otras formas, por ejemplo, como parte de un fichero de recursos. También es posible comprimir en memoria en lugar de en un fichero, y usar tuberías.

La estructura ZIENTRY

En los ejemplos anteriores sólo hemos usado esta estructura para obtener el nombre de cada fichero o el número de ficheros en el zip, pero tiene otros valores que nos pueden interesar.

Esta es la declaración de la estructura completa:

typedef struct
{ int index;                 // index of this file within the zip
  TCHAR name[MAX_PATH];      // filename within the zip
  DWORD attr;                // attributes, as in GetFileAttributes.
  FILETIME atime,ctime,mtime;// access, create, modify filetimes
  long comp_size;            // sizes of item, compressed and uncompressed. These
  long unc_size;             // may be -1 if not yet known (e.g. being streamed in)
} ZIPENTRY;

Ya conocemos los dos primeros: el índice y el nombre.

El tercero contiene los atributos del fichero.

El cuarto, quinto y sexto las fechas y horas de último acceso, creación y modificación del fichero.

El séptimo el tamaño del fichero comprimido.

El octavo, el tamaño del fichero descomprimido.

Estos dos últimos pueden ser -1, si el tamaño es desconocido, por ejemplo, si el fichero zip proviene de un canal stream.