AnteriorPosterior

7.3. Ficheros binarios

  Curso: Programación en Pascal (v5), por Nacho Cabanes

7.3. Ficheros binarios.

Hemos visto cómo acceder a los ficheros de texto y a los fichero "con tipo". Pero en la práctica nos encontramos con muchos ficheros que no son de texto y que tampoco tienen un tipo de datos claramente repetitivo.

Muchos formatos estándar como PCX, DBF o GIF están formados por una cabecera en la que se dan detalles sobre el formato de los datos, y a continuación ya se detallan los datos en sí.

Esto claramente no es un fichero de texto, y tampoco se parece mucho a lo que habíamos llamado ficheros con tipo. Podríamos emplear un fichero "de tipo byte", pero esto resulta muy lento a la hora de leer ficheros de gran tamaño.

Pero hay un alternativa: declarar un fichero sin tipo (o "fichero binario"), en el que nosotros mismos decidimos qué tipo de datos queremos leer en cada momento, a partir de lo que conozcamos sobre cómo se creó el fichero.

Ahora leeremos bloques de bytes, y los almacenaremos en un "buffer" (memoria intermedia). Para ello tenemos la orden "BlockRead", cuyo formato es:

procedure BlockRead(var F: Fichero; var Buffer; Cuantos: Word
      [; var Resultado: Word]);

donde

  • "F" es un fichero sin tipo (declarado como "var fichero: file" ).
  • "Buffer" es la variable en la que queremos guardar los datos leídos.
  • "Cuantos" es el número de datos que queremos leer.
  • "Resultado" (opcional) almacena un número que indica si ha habido algún error.

Existe otra diferencia con los ficheros que hemos visto hasta ahora, y es que cuando abrimos un fichero sin tipo con "reset", debemos indicar el tamaño de cada dato (normalmente diremos que 1, y así podemos leer variables más o menos grandes indicándolo con el dato "cuantos" que aparece en BlockRead).

Así, abriríamos el fichero con

reset( fichero, 1 );

En el caso de Turbo Pascal para MsDos, existe una limitación en cuanto a tamaño de los datos: los bloques que leemos con "BlockRead" deben tener un tamaño menor de 64K (el resultado de multiplicar "cuantos"por el tamaño de cada dato).

El significado de "Resultado" es el siguiente: nos indica cuantos datos ha leído realmente. De este modo, si vemos que le hemos dicho que leyera 30 fichas y sólo ha leído 15, podremos deducir que hemos llegado al final del fichero. Si no usamos "resultado" y tratamos de leer las 30 fichas, el programa se interrumpirá, dando un error.

Para escribir bloques de datos, utilizaremos "BlockWrite", que tiene el mismo formato que BlockRead, pero esta vez si "resultado" es menor de lo esperado indicará que el disco está lleno.

Esta vez, es en "rewrite" (cuando abrimos el fichero para escritura) donde deberemos indicar el tamaño de los datos (normalmente 1 byte).

Vamos a ver un ejemplo, que crea una copia de un fichero leyendo bloques de 2K:

(* COPIAFIC.PAS, Copia un fichero      *)
(* Parte de CUPAS5, por Nacho Cabanes  *)
 
 program CopiaFichero;
 { Sencillo y rápido programa de copia de ficheros, SIN comprobación
   de errores }
 
var
    Origen, Destino: file;
    CantLeida, CantEscrita: Word;
    NombreOrg, NombreDest: String;
    Buffer: array[1..2048] of Char;
 
begin
    Write( 'Introduzca el nombre del fichero ORIGEN... ' );
    ReadLn( NombreOrg );
    Write( 'Introduzca el nombre del fichero DESTINO... ' );
    ReadLn( NombreDest );
    Assign( Origen, NombreOrg );
    Reset( Origen, 1 );                                { Tamaño = 1 }
    Assign( Destino, NombreDest );
    Rewrite( Destino, 1 );                             { Lo mismo }
    WriteLn( 'Copiando ', FileSize(Origen), ' bytes...' );
    repeat
        BlockRead( Origen, Buffer, SizeOf(Buffer), CantLeidaspan style="color: #009900;">);
        BlockWrite( Destino, Buffer, CantLeida, CantEscrita);
    until (CantLeida = 0) or (CantEscrita <> CantLeida);
    Close( Origen );
    Close( Destino );
    WriteLn( 'Terminado.' )
end. 
 

Un último comentario: es habitual usar "SizeOf" para descubrir el tamaño de una variable, en vez de calcularlo a mano y escribir, por ejemplo, 2048. Es más fiable y permite modificar el tipo o el tamaño de la variable en la que almacenamos los datos leídos sin que eso repercuta en el resto del programa.

Antes de practicar con ejercicios, vamos a ver un segundo ejemplo más avanzado, que muestra parte de la información contenida en la cabecera de un fichero GIF, leyéndolo con la ayuda de un "record":

(* GIFHEAD.PAS, Lee la cabecera de un fichero GIF *)
(* Parte de CUPAS5, por Nacho Cabanes             *)
 
program GifHeader;
 
Type
    Gif_Header = Record                 { Primeros 13 Bytes de un Gif }
        Firma, NumVer     : Array[1..3] of Char;
        Tam_X,
        Tam_Y             : Word;
        _Packed,
        Fondo,
        Aspecto           : Byte;
    end;
 
Var
    Fich: File;
    Cabecera: GIF_Header;
    Nombre: String;
 
begin
    Write( '¿Nombre del fichero GIF (con extensión)? ');
    ReadLn( Nombre );
    Assign( Fich, Nombre );
    Reset( Fich, 1 );                           { Tamaño base: 1 byte }
    BlockRead( Fich, Cabecera, SizeOf(Cabecera) );
    Close( Fich );
    With Cabecera DO
    begin
        WriteLn('Version: ', Firma, NumVer);
        WriteLn('Resolución: ', Tam_X, 'x',
            Tam_Y, 'x', 2 SHL (_Packed and 7));
    end;
end. 
 

Ejercicio propuesto 7.3.1: Crea un programa que abra un fichero con extensión EXE (cuyo nombre introducirá el usuario) y compruebe si realmente se trata de un ejecutable, mirando si los dos primeros bytes del fichero corresponden a una letra "M" y una letra "Z", respectivamente.
Ejercicio propuesto 7.3.2: Crea un programa que pida al usuario dos nombres de ficheros y los compare byte a byte, para decir finalmente si son iguales o no.
Ejercicio propuesto 7.3.3: Crea un programa que pida al usuario el nombre de un fichero y vuelque a un segundo fichero todo el contenido del primero que sea imprimible (todos los caracteres por encima del 31). El segundo fichero tendrá el mismo nombre que el primero, pero se le añadirá al final ".txt" para que se pueda abrir con cualquier editor de texto.
Ejercicio propuesto 7.3.4: Crea un programa que abra un fichero con extensión BMP (cuyo nombre introducirá el usuario) y mire si parece ser una imagen BMP válida, viendo si los dos primeros bytes del fichero corresponden a una letra "B" (posición 0) y una letra "M" (posición 1), respectivamente. Si es así, mostrará el ancho de la imagen (bytes 18 a 21) y su alto (bytes 22 a 25).
Ejercicio propuesto 7.3.5: Crea un programa que muestre la información sobre un fichero MP3, a partir de estos detalles: Muchos ficheros MP3 incluyen una "cabecera" al final del fichero, conocida como"ID3 versión 1". Se trata de un bloque de tamaño fijo de 128 bytes al final del fichero en cuestión. Si es fichero contiene realmente es cabecera ID3 V1, aparecerán en primer lugar eso caracteres TAG, luego el Título (30 caracteres), el Artista (30 caracteres), el Álbum (30 caracteres), el Año (4 caracteres), un comentario (30 caracteres) y el género musical (un carácter). Todas las etiquetas usan caracteres ASCII (terminados en espacios o en carácter nulo), excepto el género, que es un número entero almacenado en un único byte. El género musical asociado a cada byte está predefinido en el estándar e incluye definiciones de 80 géneros, numerados del 0 al 79, aunque algunos programas de reproducción han ampliado por su cuenta los géneros definidos (a partir del número 80).

7.4. Abrir exclusivamente para lectura.

Puede interesarnos leer un fichero que se encuentra en un CdRom/DvdRom, o en una red de ordenadores. Normalmente, tanto los CdRom como las "unidades de red" se comportan de forma muy similar a un disco duro "local", de modo que no supondrá ningún problema acceder a su contenido desde MsDos y desde Windows. En cambio, si intentamos leer datos desde un CdRom o desde una unidad de red, con Turbo Pascal 7, de la misma manera que hemos hecho hasta ahora, nos podemos encontrar con que no lo consigamos, sino que obtengamos un error de "File acces denied" (se ha denegado el acceso al fichero).

El motivo es que Turbo Pascal intenta abrir el fichero tanto para leer de él como para escribir en él. Esto es algo útil si utilizamos nuestro disco duro o (en equipos antiguos) un diskette, pero normalmente no tendremos la posibilidad de grabar directamente datos en un CdRom convencional, ni tendremos a veces permisos para escribir en una unidad de red.

Pero podemos cambiar esta forma de comportarse de Turbo Pascal. Lo haremos mediante la variable FileMode, que está definida siempre en Turbo Pascal (está en la unidad System). Esta variable normalmente tiene el valor 2 (abrir para leer y escribir), pero también le podemos dar los valores 1 (abrir sólo para escribir) y 0 (abrir sólo para lectura).

Por tanto, la forma de abrir un fichero que se encuentre en un CdRom o en una unidad de red sería añadir la línea

FileMode := 0;

antes de intentar abrir el fichero con "reset".

Ejercicio propuesto 7.4.1: Crea un nueva versión del programa 7.3.1, que abre un fichero con extensión EXE (cuyo nombre introducirá el usuario) y comprueba si realmente se trata de un ejecutable, mirando si los dos primeros bytes del fichero corresponden a una letra "M" y una letra "Z", respectivamente. Esta nueva versión debe abrir el fichero en modo sólo de lectura.

Actualizado el: 21-07-2014 00:24

AnteriorPosterior