[UACA] [/\]
Revista
Acta Académica


Universidad Autónoma de Centro América 

[<=] [home] [<>] [\/] [=>]

"genridx.h"
Una interfaz uniforme para el uso de archivos indizados en C[*]

Adolfo Di Mare



Resumen [<>] [\/] [/\]

      "genridx.h" es una interfaz que unifica el acceso a archivos indizados, lo que permite escribir programas C que son parcialmente independientes del método de acceso utilizado. Esta independencia física del método de acceso comporta un costo despreciable en espacio y tiempo de ejecución , y le brinda al programador la oportunidad de cambiar el método de acceso con facilidad. Se discuten además varias ideas sobre cómo usar archivos en programas C. Como ejemplo, se discute una implementación completa de la interfaz para el método de acceso Btrieve de la casa Novell. "genridx.h" is an unyfied interface to access indexed files in C, to support writting programs that are partially independent from the access method in use. This physical independence has a negligible cost both in execution time and space, and gives the programmer the oportunity to change the access method with ease. Some ideas on how to use record files in C are also discused. As an example, a complete implementation to interface Novell's Btrieve is described.

Introducción [<>] [\/] [/\]

      El lenguaje de programación C es, cada vez más, el escogido para implementar los más sofisticados sistemas computacionales, principalmente en el ambiente de las micro y mini computadoras. Este uso se debe, en gran medida, a que C es un lenguaje que permite al programador expresar clara y concisamente algoritmos, lo que no siempre es tan sencillo con otros lenguajes. En el ambiente de las máquinas grandes C todavía no ha tenido tanto impacto, aunque ya es usado para programar algunas partes de sistemas operativos.

      Para desarrollar aplicaciones administrativas en C es necesario complementar al lenguaje con al menos dos tipos de herramienta. La primera es un manejador de interfaz con el usuario, la que permite al programador definir fácilmente las pantallas de la aplicación. Ejemplo de éste tipo de biblioteca es el C Tools Plus para MS/DOS o el paquete Curses en UNIX.

      El segundo tipo de herramienta a utilizar es un manejador de archivos, lo que permite al programador crear y dar mantenimiento a ficheros de registros [12]. El servicio que brinda un manejador de archivos o método de acceso es el permitir que los registros del archivo puedan ser recuperados con base en el contenido de alguno de sus campos, y no simplemente por su posición física dentro del archivo. Esta forma de acceso se conoce como acceso por medio de llaves, y es fundamental para el manejo de bases de datos. De otra manera no sería posible obtener los registros de una persona almacenados en un computador si se tiene el número de identificación de la persona.

      Ejemplo de manejadores de archivos son el Btrieve de Novell [9] para ambientes IBM/pc y el CBtree en UNIX. Algunos vendedores de programas llaman a estos métodos de indización sistemas de bases de datos, aunque este término debe reservarse para sistemas de administración de archivos más sofisticados y completos que los manejadores de archivos a los que se refiere este documento.

      En este artículo se discute una interfaz uniforme para este tipo de bibliotecas. A diferencia de otros enfoques ([4], [7], [10]), esta interfaz permite escribir programas que mantienen una independencia parcial del sistema de archivos utilizado. Esta independencia física del método de acceso implica un costo muy reducido en espacio y en tiempo de ejecución, y le brinda al programador la oportunidad de cambiar el método de acceso a archivos con gran facilidad.

      Aunque la interfaz que se presenta como ejemplo ha sido implementada en Borland C++ v3.1 [1] en el contexto del manejador de archivos Btrieve de Novell, es relativamente sencillo adaptarla a cualquier otro manejador de archivos o a otra versión del lenguaje C. Aunque C++ es el lenguaje que más apoyo tiene ahora, por lo menos en las publicaciones técnicas, esta interfaz fue escrito en C porque en mucho ambientes, principalmente en Unix, C++ todavía no es una tecnología que cuente con el apoyo que C ya tiene. Las ideas que aquí se presentan pueden ser desarrolladas también usando las plantillas de C++ [3] pero todavía la mayoría de los compiladores no soportan esta nueva construcción sintáctica.

      Esta interfaz es útil para métodos de acceso que permiten accesar registros de uno en uno (one record at a time access). En el caso de los sistemas administradores de bases de datos relacionales, que en general usan SQL, con esta interfaz se pueden recibir los tuples de uno en uno, pero la interfaz no tiene la capacidad de recibir varios registros simultáneamente. Como la mayoría de los sistemas relacionales poseen interfaces para el lenguaje C ([8], [11]), esta interfaz es también aplicable en estos casos.

      Los programas que acompañan a este artículo están documentado en la lengua inglesa, que es la lengua de la computación. Es más cómodo producir código en el lenguaje universal de la informática pues entonces es más fácil intercambiarlo con otros programadores. El código C en este artículo está disponible en esta localización Internet:
      http://www.uaca.ac.cr/actas/1994nov/genridx.zip

La programación en C [<>] [\/] [/\]

      Un programa C siempre está compuesto de dos tipos de archivos: archivos de declaración y archivos de código. En los primeros se definen los prototipos o encabezados de cada una de las funciones C usadas para implementar un sistema, y en los segundos está el código de cada función. Los primeros indican, grosso modo, el "qué" hacer (interfaz), mientras que en los segundo se dice "cómo" hacerlo (implementación).

      Un archivo de declaraciones en general tiene un nombre corto, que en general es de hasta ocho caracteres, y su extensión es ".h" (punto-hache, dot-h en inglés). Los archivos de código generalmente tienen extención ".c" (o ".cpp" para C++).

      El C ANSI incluye una biblioteca estándar de funciones, cuyos prototipos están descritos en los archivos de encabezados estándar. Por ejemplo, para usar el procedimiento printf(...), la manera de hacerlo es la siguiente:

#include <stdio.h>  /* declara printf(...) y otros */
/*....*/
printf(...);        /* uso de la función printf()  */

      Esta forma de verificar interfaces entre los diversos módulos que conforman un sistema (archivos en el caso de C) es bastante rudimentaria, pero efectiva. Otros lenguajes (como Modula 2, ADA y Turbo Pascal) cuentan con esquemas mucho más elaborados para verificación de interfaces, pero requieren de ligadores de eslabonamiento (linkers en inglés) mucho más sofisticados. Para programar en C es necesario entender bien cómo funciona el sistema de verificación de interfaces; quien no lo conoce, en general, no puede hacer uso adecuado del lenguaje.

Definición de archivos [<>] [\/] [/\]

      Al programar una aplicación comercial es necesario utilizar varios archivos de datos. Por ejemplo, un sistema de información necesita archivos de personas, de cuentas, movimientos, etc.

+NUMERO....+CHAR+08+.+
+NOMBRE....+CHAR+15+.+
+SEXO......+CHAR+01+0+                    /* ENCABEZADO   */
+SUELDO....+DEC.+10+2+
+NACIDO....+DATE+08+.+
10-03-24MAYELA VARGAS030000.00F561010
20-00-32JUAN PELOTAS 000000.00M751023     /* REGISTROS DE */
30-15-99RAMBO RAMIREZ000112.00M700802     /* DATOS        */
Figura 1: Ejemplo del encabezado Dbase para el archivo de personas

      En un ambiente de bases de datos lo usual es que el sistema de acceso incluya una facilidad llamada diccionario de datos que permite definir todos los archivos y sus campos. Manejadores de archivos, como Dbase, incluyen un archivo de encabezado (header) dentro de cada archivo que contiene la descripción de los campos, como se muestra en la Figura 1. De esta manera todos los programas que usan un archivo Dbase tienen acceso a su descripción actualizada.

      En general, los diccionarios de datos de los sistemas de bases de datos utilizan estructuras externas a cada archivo para almacenar esas definiciones. Por ejemplo, el Unify para UNIX almacena el diccionario de datos en archivos de un formato especial; en este caso, el diccionario de datos también está descritos en el mismo diccionario.

      Para el programador Dbase no es problema usar un archivo desde varios programas de aplicación, pues el sistema puede leer la descripción del archivo al abrirlo. El programador db_Vista III en UNIX usa varios archivos de encabezado (.h) en los que se describen los campos de cada archivo. Estos encabezados son generados por el utilitario de mantenimiento del diccionario de datos.

      Esta disciplina en el uso de archivos, impuesta en unos casos por el manejador de archivos (como es el caso en Dbase), y en otros por el sistema de bases de datos (com es el caso en db_Vista III), es muy beneficiosa para el programador, pues logra que la definición de cada archivo esté disponible para todos los programas que los usan. Esta es, precisamente, una de las justificaciones originales para pagar el alto precio de utilizar sistemas de bases de datos: lograr que todos los programas que utilizan un mismo archivo tengan acceso a su definición actualizada. De lo contrario sucede que cuando cambia la definición del archivo entonces unos programas lo actualizarán correctamente y otros no, lo que introduce inconsistencias en los datos almacenados.

/* @(#)    usaper.c     NO-Copyright 1991 Adolfo Di Mare       */

/* Ejemplo de como hacer MAL las cosas al usar archivos        */

#include <stdio.h> /* declara fopen() y fread() */
/* ... */
struct persona {
    char NUMERO[08];    /* declara el registro sin usar */
    char NOMBRE[15];    /* un archivo de cabezal (.h)   */
    char SEXO;
    char SUELDO[10];
    char NACIDO[08];
} var1, var2;
FILE *f;

main() {
    /* ... */
    f = fopen("personas.dat", "rt");   /* abre el archivo */
    fread(var1, sizeof(var1), 1, f);   /* lee el registro */
    /* ... */
}
/* End of file usaper.c */
Figura 2: Uso usual [erróneo] de archivos en C

      Tal vez parezca innecesario exigir que se declare bien cada archivo utilizado en un programa (¡pues es obvio que hay que hacerlo!), pero lo cierto es que muchos programadores C no lo hacen. El autor ha leído muchos programas C en que los archivos se declaran no en un encabezado (.h), como es propio, sino en el código de implementación (.c). Por ejemplo, para usar un archivo de personas como el de la Figura 1, muchos programadores C escriben el código como se muestra en la Figura 2.

      Como el programa de la Figura 2 funciona adecuadamente, después de escribirlo el programador se dedicará al siguiente trabajo. El problema surge cuando se necesita usar el mismo archivo en varios programas, que es lo usual en cualquier sistema de información. En este caso, siempre que se requiera cambiar la estructura del registro de personas será necesario cambiar manualmente todos los programas que lo usan. Deberán además documentarse todos los nombres que se usan en el archivo de personas, pues unos llamarán a la estructura de una forma ("persona" como en el ejemplo), y otros de otra ("PERSONA" o "Persona" son candidatos igualmente válidos). El uso de archivos de encabezados para describir archivos evita todos estos problemas.

Uso de encabezados para describir archivos [<>] [\/] [/\]

      La solución al problema de definir el mismo archivo en varios programas C es simple: basta definir un archivo de encabezado (.h) en donde estén descritos todos los datos de una persona, como se muestra en el Listado 1. Para accesar el archivo de personas el programador debe hacer lo siguiente:
  1. En cada programa que necesite usar el archivo de personas debe incluirse el archivo de encabezado "person.h" usando la directiva #include del preprocesador del lenguaje C:
    #include "person.h"
  2. Si se necesita definir varias variables de tipo "person", basta hacerlo usando la sintaxis C usual:
    float sueldo, planilla[MAX_planilla];

    person p1, p2, p[20]; /* p es un vector de 20 personas */
  3. Siempre que se necesite cambiar la definición del archivo de personas deberá cambiarse el archivo "person.h", para luego recompilar todos los programas que lo utilicen (lo que es relativamente sencillo si se usa el programa MAKE para recompilar programas).

En general, la forma de utilizar los archivo de encabezado en una aplicación es la siguiente:

  1. Para cada archivo usado en un programa debe definirse un archivo de encabezado en el que se describan los campos del archivo.
  2. Al definir los campos del archivo, es menester también definir constantes que indiquen las propiedades de cada campo, y en particular el largo de cada una de las hileras. De esta manera se evita llenar los programas de números mágicos, con la consecuente reducción en el costo de darle mantenimiento a los sistemas.
  3. Los cambios en la definición del archivo deben hacerse en el encabezado, de forma que la nueva definición se refleje en todos los programas que accesen el archivo.

      El uso de encabezados para describir los archivos de una aplicación es claramente una necesidad. Los programadores que no utilicen encabezados no sólo pecan por ignorantes, sino que además están incrementando innecesariamente el costo de construcción y mantenimiento de los sistemas de aplicación[1].

      Sin embargo, el uso de archivos de encabezado no es el único ingrediente necesario para implementar una interfaz independiente del método de acceso. Antes de discutir en detalle la forma de lograr esta independencia es necesario discutir las ventajas de usar el macro preprocesador del lenguaje C.

El macro preprocesador de C [<>] [\/] [/\]

      Al programar en C cualquier programador usa muchas veces las macros, las que se definen usando la directiva del preprocesador #define. Ejemplo clásico de macros C son MAX() y MIN():
#define MAX(x,y) ((x)>(y) ? (x) : (y))
#define MIN(x,y) ((x)<(y) ? (x) : (y))

      Antes de compilar el programa cada macro es sustituida por los símbolos que aparecen en su definición, sustiyendo los argumentos literalmente. El producto de esta sustitución es lo que el compilador C realmente digiere. Por ejemplo, si el programador escribe en su programa MAX(a+b,MIN(c,d)), el resultado es que el compilador procese:

((a+b)>(((c)<(d) ? (c) : (d))) ? (a+b) : (((c)<(d) ? (c) : (d))))

      También el preprocesador permite la compilación condicional de programas, con lo que es posible compilar unas partes del código dependiendo del valor de ciertas macro variables (definidas en tiempo de compilación).

      Mediante la compilación condicional y el uso de macros es posible escribir código C parametrizable. En su implementación, la interfaz que se presenta en este artículo requiere de estas dos facilidades del preprocesador C.

Interfaz genérica "genridx.h" [<>] [\/] [/\]

      Para crear una interfaz independiente del manejador de archivos es necesario definir, exactamente, las formas de acceso a los archivos indizados. Luego es necesario programar estos verbos, usando la idiosincrasia del manejador de archivos. El Listado 2 es el texto del archivo "genridx.h" (GENeRic InDeX) en el que se definen los verbos para accesar archivos indizados.

      Las macros de "genridx.h" son la base sobre la que se construye la interfaz para archivos indizados. El programador usuario siempre usará estas macros para usar los índices de sus archivos. La idea es que cada macro se transforma en una invocación a una función o a otra macro, y el nombre del manejador de archivos sirve para distinguir la interfaz en uso. De esta manera es posible utilizar en el mismo programa diversos manejadores de archivos, o sustituir uno por otro cuando así lo requiera la aplicación.

/* @(#)   btrieve.h             Copyright 1990 Adolfo Di Mare   */
/*      Interface to the Btrieve Record Manager, version 4      */

#ifndef _btrieve_h
#define _btrieve_h  /* evita inclusión múltiple */

#ifdef __cplusplus
extern "C++" {       /* compatibilidad con C++ */
#endif

#include "genridx.h"

/* Función de interfaz con Btrieve */
int BTRV(int OP, char POS_BLOCK[], char DATA_BUF[],
         int *DATA_LEN, char KEY_BUF[], int KEY_NUM);

enum btrieve_constants {
    BT_MAX_KEYS = 24,       /* max indices por archivo */
    POSTION_BLOCK_SZ = 128, /* área de trabajo Btrieve */
    /* ...  */
};

typedef struct { /* ... */ } btr_filespec;  /* archivo */
typedef struct { /* ... */ } btr_keyspec;   /* llaves  */

typedef struct {   /* Descriptor de archivos para "btrieve.c" */
    /* Campos publicos obligatorios */
    INT2         key_number;  /* número de llave en uso */
    int          locking;     /* TRUE al usar bloqueo   */
    INT2         status;      /* último status Btrieve  */

    /* Campos exclusivos de Btrieve */
    char       * file_name;   /* nombre DOS del archivo    */
    const char * type_name;   /* tipo de registro          */
    char       * key_buffer;  /* memoria para la llave     */
    btr_spec   * stat;        /* descriptor Btrieve        */
    char      pos_block[POSTION_BLOCK_SZ]; /* área Btrieve */

    INT4         last_drn;    /* drn del último registro      */
    INT2         open_mode;   /* modo de apertura del archivo */
    INT2         real_length; /* longitud real del registro   */
    INT2         last_length; /* longitud del último registro */

} Btrieve_access;
#define Btrieve_declare  Btrieve_access

/* Constantes para Btrieve */
#define Btrieve_OK                  BT_OK
#define Btrieve_NO_MEM              BT_INSUFFICIENT_MEMORY
/* ... */

/* Operación genérica de lectura [Verbos GET] */

xdret_t Btrieve_generic(
    short            op_code,     /* Btrieve operation code      */
    Btrieve_access * file         /* contains all the file data  */
);

/* Códigos de operación Btrieve */
#define BT_GET_EQUAL   5
#define BT_GET_NEXT    6
#define BT_STEP_NEXT  24

#define Btrieve_find(file)                                       \
    (                                                            \
    Btrieve_build_key(                                           \
        (file).stat,        (file).key_number,                   \
        (file).key_buffer,  (file).data_buffer                   \
    ),                                                           \
    Btrieve_generic(                                             \
          BT_GET_EQUAL,        /* operation code         */      \
        & (file)               /* file descriptor        */      \
    )                                                            \
    )

#define Btrieve_next(file_desc)                                  \
    Btrieve_generic(                                             \
          ( DRN_ACCESS  == (file_desc).key_number                \
            ?  BT_STEP_NEXT                                      \
            :  BT_GET_NEXT     /* operation code         */      \
          ),                                                     \
        & (file_desc)          /* file descriptor        */      \
    )

#define Btrieve_select(file_desc,  key)                          \
    ( (file_desc).key_number =                                   \
        (                                                        \
            ( ( DRN_ACCESS  <= (short) (key))   &&               \
              (short) (key) < (file_desc).stat->file.n_index )   \
            ? (short) (key)                                      \
            : (file_desc).key_number                             \
        )                                                        \
    )

#ifdef __cplusplus
}
#endif

#endif  /* _btrieve_h */
Figura 3: btrieve.h: extracta del archivo

      Al crear la interfaz para el Btrieve fue necesario crear varias macros y funciones cuyo nombre comienza con "Btrieve_", las cuales se encuentran en el archivo de encabezado llamado "btrieve.h", parte del cual se muestra en la Figura 3. Este archivo incluye, usando la directiva del preprocesador #include, a "genridx.h", el archivo en que se definen los verbos que forman la interfaz de acceso a archivos indizados. Cada macro o función definida en el archivo de encabezado "btrieve.h" es invocada, indirectamente, cuando el programador utiliza los verbos de acceso definidos en "genridx.h". El programador nunca utiliza directamente las macros y funciones definidas en "btrieve.h". La independencia del manejador de archivos se logra al cambiar el valor del primer argumento de cada uno de los verbos de acceso definidos en "genridx.h".

      Por ejemplo, la invocación de la macro xd_select(Btrieve, bFILE, 3) se transforma en Btrieve_select(bFILE, 3), que a su vez es una invocación a una macro (definida en "btrieve.h"). Si el programador quisiera usar el manejador de archivos DDMPC, entonces bastará que use xd_select(DDMPC, bFILE, 3), para así obtener DDMPC_select(bFILE, 3). Estas invocaciones, a su vez, son macros que se transforman hasta obtener el código C necesario ejecutar la operación "xd_select()", que sirve para indicar cuál es el índice a usar para un archivo.

      En la práctica, lo que se hace es definir también el nombre del manejador de archivos como una macro. Por eso en el archivo "person.h" (Listado 1), aparece el renglón:
      #define dm Btrieve
para usar en el programa xd_select(dm,bFILE,3). Para cambiar de manejador de archivo basta cambiar la macro "dm":
      #define dm DDMPC
Este es un ejemplo de la parametrización que puede obtenerse mediante el uso de macros: el manejador de archivos es un parámetro más cuyo valor determina el código C que el compilador procesa: en este ejemplo en unos casos se usa el de Btrieve y en otros el de DDMPC.

      Todas las macros definidas en "genridx.h" tienen en común el primer parámetro, llamado "xd". Este primer argumento se sustituye por el nombre del manejador de archivos. En el caso de Btrieve, el valor de la macro "xd" debe ser "Btrieve", para el DDMPC el parámetro "xd" debe ser "DDMPC".

      A continuación se define, con suficiente formalidad, cada uno de los verbos de acceso para usar archivos indizados. Más adelante se presenta un ejemplo completo que ayudará a entender bien cómo usar la interfaz "genridx.h", que está en el Listado 3.

Verbos de acceso a archivos indizados [<>] [\/] [/\]

      Los verbos xd_open(), xd_key_file(), xd_close() permiten abrir y cerrar un archivo y sus archivos de índice. El verbo xd_clear() permite borrar todo el contenido de un archivo sin borrar el archivo totalmente.

      La operación xd_reindex() sirve para reconstruir uno de los archivos de índice del archivo. No existe una operación para reconstruirlos todos en una sóla operación. Es notable que Btrieve no provee esta operación, pues ese método de acceso a sido diseñado de forma que nunca pueda darse que un índice esté desactualizado, pues siempre que se usa el archivo automáticamente Btrieve también usa todos sus índices.

      Contrario a lo que dicta el sentido común, al abrir un archivo no se obtiene acceso a todos sus índices; esta restricción ayuda a consiliar esta interfaz con diversos manejadores de archivos que administran el acceso al archivo de datos separadamente del acceso a los archivos de índices (como Dbase, CBtree, y otros). La parte menos portable de toda la interfaz es precisamente la operación xd_open(), pues en general cada manipulador de archivos tiene formas diferentes de especificar los modos de acceso y de sincronización en ambientes multiusuario disponibles al programador.

      En la práctica, si el programador cambia de manejador de archivos debe reprogramar todas las invocaciones a xd_key_file() y a xd_open(), lo que en general no es un grave problema, más aún si usa compilación condicional para evitar borrar el código viejo. El xd_open() es la parte de esta interfaz que obliga al autor a confesar que con "genridx.h" sólo se logra una independencia parcial del manejador de archivos.

      La macro xd_error() invoca a una función que transforma un código de error retornado por el manejador de archivos en una tira de caracteres que lo describe. xd_file() y xd_type() retornan tiras que tienen el nombre del archivo en proceso y el tipo de ese archivo. Por ejemplo, al usar el archivo "PERSON.DAT", xd_file() retornará (un puntero a) la hilera "PERSON.DAT" y xd_type() retornará "person". xd_status() sirve para recuperar el código de retorno o de error de la última operación de acceso al archivo indizado.

      Para mejorar la independencia del manejador de archivos se incluye dentro de "genridx.h" la definición de las constantes xd_OK(), xd_NO_MEM(), xd_CRASH(), xd_NOT_FOUND(), xd_DUPLICATE(), xd_LOCKED(), xd_BOF(), xd_EOF(), que son códigos de retorno muy utilizados en cualquier manejador de archivos.

      xd_declare() y xd_define() permiten declarar y definir una variable que contiene los campos necesarios para implementar el acceso a un archivo indizado. Esta variable es un descriptor de archivo, y su función es muy similar a las variables de tipo FILE* definidas en el archivo de encabezado estándar <stdio.h>. El uso de variables descriptoras de archivos posibilita el mantener muchos archivos diferentes abiertos. Cada descriptor de archivo sirve para accesar un archivo indizado usando cualquiera de sus índices.

      Una variable declarada con xd_declare() es una estructura que al menos siempre contiene los siguientes campos:

INT2 key_number;  /* número de la llave en uso          */
int  locking;     /* TRUE cuando se está usando bloqueo */
INT2 status;      /* último código de error             */

      INT2 es una macro definido en "genridx.h" la que se transforma en un tipo entero con signo de dos bytes. Este truco es usado para lograr un poco de independencia del compilador C en uso.

      El descriptor de archivo debe contener campos que permitan obtener el nombre del archivo, el nombre del tipo, y todos los que sean necesarios para implementar la interfaz definida en "genridx.h". Por ejemplo, puede ser necesario tener un campo suficientemente grande para contener la llave más larga del archivo, o un área de control que para el manejador de archivos. Como los descriptores de archivos se definen usando una macro es posible aislar el código para usar el manejador de archivos de sus particularidades.

      Los siguientes son los verbos para recurperar registros del archivo indizado:
      xd_find() Obtiene un registro con base a su llave
xd_seek() Obtiene un registro con base a su llave
xd_match() Búsqueda por llave parcial (solo hileras)
xd_first() Obtiene el primer registro
xd_last() Obtiene el último registro
xd_next() Obtiene el siguiente registro
xd_prev() Obtiene el registro anterior

      Estos son los verbos más utilizados, pues le permiten al programador desplazarse en un archivo usando una llave. La diferencia entre xd_find() y xd_seek() radica en la forma en que el programador especifica la llave. Antes de usar las operaciones xd_next() y xd_prev() es necesario establecer una posición en el índice por medio de alguna de las otras operaciones.

      Para usar xd_find() el programador no tiene que contruir la llave de acceso, sino que deja que lo haga el administrador de archivos, mientras que para xd_seek() el programador debe construir la llave y pasársela al manejador de archivos.

      Por ejemplo, al usar el archivo "PERSON.DAT", para obtener un registro mediante el xd_find() el programador debe insertar en una variable de tipo "person", en los campos que forman la llave, los valores de la llave del registro que desea recuperar (esta definición está en el Listado 1). Si usara el xd_seek(), entonces el programador construiría la llave y la pasaría como el último argumento de xd_seek(). Estas son las dos formas de acceso que es usual encontrar en la mayoría de los programas.

      Cual método es más atractivo es una cuestión de opinión personal, por lo que en esta interfaz se proveen los dos. Para llaves simples es más útil el primero, y sucede lo contrario para llaves compuestas. El xd_seek() es útil en aquellos casos en que una llave no está compuesta de varios campos, por lo que el uso de xd_find() más bien hace que el programa sea menos claro. xd_seek() debe usarse para llaves no segmentadas, o de lo contrario el programador estará obligado a escribir mucho código que se encargue de construir la llave.

      xd_match() es un verbo de acceso que tiene los mismos parámetros que xd_find(), y que en general se usa sólo para llaves alfabéticas no segmentadas. El xd_match() regresa el registros cuya llave tiene como prefijo el valor especificado como argumento de xd_match(). En muchas aplicaciones es importante identificar registros mediante llaves parciales. Es posible duplicar el efecto de xd_match() usando xd_find() junto con xd_next().

      Todos los manejadores de archivos definen de alguna forma punteros directos a los registros almacenados, pues los registros deben ser almacenados de forma que sea posible recuperarlos muy rápido si se conoce su posición física en el archivo. Esta posición se llama número interno de registro (DRN: Data Record Number). En general, el formato de los DRN's es muy diferente para cada manejador de archivos, aunque sí es posible afirmar que un DRN puede representarse como un entero de cuatro bytes (o sea como un long). En el caso de Btrieve, esos números internos contienen referencias a páginas y bloques que no es fácil adivinar; lo importante es saber que los DRN son números no consecutivos.

      Las operaciones xd_get() y xd_drn() permiten acceso directo a los registros. xd_get() permite recuperar un registro si se sabe su localización (DRN), y xd_drn() permite obtener el puntero a un registro que acaba de ser obtenido.

      xd_get() tiene una restricción fundamental, pues cuando se accesa un registro de manera directa no establece la posición respecto al índice que está en uso. Esto quiere decir que el resultado de usar xd_next() o xd_prev() después de usar xd_get() no resultará en accesar el registro siguiente en el orden definido por la llave de acceso. Esta restricción es rara, pero en la realidad muchos manejadores de archivos no mantienen la posición en el índice después de un acceso directo.

      xd_drn() es una operación fundamental para el uso de archivos en modo multiusuario, pues para desbloquear un registro es necesario haber obtenido previamente, mediante esta operación, su DRN.

      Las operaciones xd_ins(), xd_mod() y xd_del() permiten insertar, modificar y borrar registros del archivo. Al modificar el archivo, estas operaciones también actualizan todos los índices que han sido abiertos por medio de xd_key_file().

      xd_select() permite seleccionar la llave a utilizar en las operaciones de acceso, pues en general un archivo puede tener varias llaves diferentes. El programador Dbase no debe confundir esta selección con el "select" de Dbase, con el que se selecciona un archivo y no su índice. Mediante el xd_select() el programador escoge cuál es la llave por la que accesará el archivo. Esta escogencia tiene relevancia para todas las operaciones de acceso [xd_find(), ..., xd_prev(), xd_get(), etc.], pues define cuál es el la llave que se usará para el acceso a varios registros.

      La operación xd_select(dm, f, DRN_ACCESS) cambia el significado de los verbos de acceso [xd_find(), ..., xd_prev(), etc.]. Por ejemplo, xd_first() no retornará el primer registro respecto a un índice, sino aque que está físicamente almacenado de primero en el archivo. DRN_ACCESS es un número de archivo especial, que sirve para accesar en secuencia física los registros del archivos.

      xd_use_locks() sirve para que las operaciones de acceso se hagan usando bloqueo de registros, lo que es necesario para accesar archivos en modo multiusuario. En un ambiente monousuario únicamente puede existir un usuario trabajando con el archivo, pero en un escenario multiusuario varios programas pueden tratar de accesar el mismo registro simultáneamente, con lo que es necesario "enllavar" o "bloquear" un registro antes de usarlo. xd_locking() le permite al programador definir si cada operación de acceso requiere que el registro accesado sea también bloqueado.

      Si el ambiente en que un programa corre no es multiusuario, entonces la interfaz no debe permitir que registro alguno sea bloqueado. De esta manera es posible desarrollar programas en una computadora personal, y luego trasaladarlos a un ambiente de producción multiusario.

      xd_locking(dm,f) retorna TRUE si las operaciones sobre el archivo "f" se hacen usando bloqueo, y si no retorna FALSE.

      El verbo xd_unlock() le sirva al programador para desbloquear un registro que fue bloqueado anteriormente, cuando fue accesado. Esta operación requiere que el programador especifique el DRN del registro a liberar, por lo que el programador debe asegurarse de obtenerlo inmediatamente después de que accesa el registro, usando xd_drn(). No existe una forma de desbloquear todos los registros bloqueados de un archivo, aunque muchos manejadores de archivos sí proveen este servicio. En general, en aquellas aplicaciones en que se accesan los registros de un archivo de uno en uno, lo usual es bloquear sólo un registro del archivo, por lo que en la práctica esta restricción no afecta mucho al programador de aplicaciones.

      xd_rec_count(xd, f) retorna el número de registros que contiene el archivo indizado "f". Esta operación es usada pocas veces, pero el no disponer de ella hace muy difícil la construcción de programas.

      Las macros xd_field_kpos() y xd_field_size() son usadas para definir los campos de un archivo que componen una llave. La primera sirve para determinar la posición de cada campo en el registro y la segunda su tamaño.

      Por último, al escribir el archivo de encabezado de un archivo indizado el programador debe también declarar un procedimiento que sirve para inicializar un registro del archivo. Por ejemplo, en el caso de archivo "PERSON.DAT", en el archivo de encabezado "person.h" debe definirse la función init_person(person*), que permite inicializar un registro de persona. En C++, esto puede complementarse definiendo el constructor de la clase "person".

      A continuación se describe el programa "person.c", que aparece completo en el Listado 3, el que ejercita todos los verbos de la interfaz uniforme definida en "genridx.h"

La implementación de "person.c" [<>] [\/] [/\]

      La función de "person.c" es la de crear un archivo de personas con dos llaves, y accesarlo usando los verbos definidos en "genridx.h". Si el archivo existe, entonces su contenido es borrado, antes de ser creado de nuevo.

      El programa "person.c", en sus primeras líneas, tiene la siguiente directiva de compilación condicional:
      #ifdef CREATE
que sirve para evitar que se compile todo el programa cuando CREATE no está definida. Cuando la variable del preprocesador llamada CREATE está definida entonces es posible producir, mediante compilación condicional, el programa "person.exe". Este programa incluye la definición de una estructura (btr_spec) que describe las calidades de un archivo Btrieve de personas. El código para crear el archivo en "person.c" es totalmente dependiente del manejador de archivos Btrieve. Como lo usual es crear cada archivo una sóla vez, y dado que es relativamente raro crear archivos, esta operación no aparece en la interfaz estándar definida en "genridx.h". Además, la forma de la crear archivos varía mucho de un manejador de archivos a otro.

      El programa "person.c" es muy simple: crea un archivo vacío de personas indizado por dos llaves, una de las cuales es compuesta. Luego lo llena de registros y por último lo accesa usando cada una de las dos llaves del archivo. De esa manera se ejercitan todos los verbos de acceso a archivos definidos en "genridx.h".

      En la práctica, un programador creará un archivo (.c) para cada uno de los encabezados de archivos indizados (.h). En el caso del archivo "PERSON.DAT", la ventaja de delegar en "person.c" la creación del archivo inicial es que el programa de aplicación se simplifica, pues se escribe asumiendo que el archivo en que se trabajará ya existe. Otra ventaja de usar archivos de implementación como "person.c" es que la definición del archivo queda escrita en C, y no es necesario utilizar utilitarios esotéricos para crear archivos vacíos (como el BUTIL.exe de Novell). En la etapa en que se está probando el programa de aplicación es muy útil tener un programita pequeñito (person.exe) que al ser corrido crea de nuevo un archivo.

      Al programar su aplicación, el programador debe incluir el archivo "person.h" en todos aquellos módulos que usen un archivo de personas. También debe compilar "person.c", pero sin definir la variable del preprocesador CREATE, pues es en este módulo en donde define la rutina init_person(), que se encarga de inicializar variables de tipo "person". Cuando la variable de preprocesador CREATE no está definida, lo único que se compila en "person.c" es este procedimiento de inicialización[2].

      El programa "person.c" contiene la variable "file_description", la que es inicializada con la descripción Btrieve del archivo de personas. Como "person.c" incluye al archivo "person.h", el que a su vez incluye a "btrieve.h", en "person.c" puede hacerse referencia a todos los tipos definidos en "btrieve.h". Entre estos tipos se encuentra "btr_spec", que es una estructura con la que se definen los datos de un archivo manejado por Btrieve.

      La descripción de cómo se define un archivo Btrieve está fuera del alcance de este artículo[3]. Mas basta decir que si el manejador de archivos fuera otro, entonces la forma de describir el archivo sería totalmente diferente. Lo importante es que, cuando la variable del preprocesador CREATE está definida, al compilar "person.c" se obtiene el programa "person.exe", que se encarga de crear un archivo de personas.

      Luego de definir las cualidades del archivo Btrieve a crear, se define el vector de personas llamado "people", el que se incializa con algunos datos de prueba. También se define la variable "buff" que se usará luego para recuperar registros.

      Seguidamente se define la variable "bFILE", por medio de xd_define(). La variable "bFILE" es un descriptor de archivo para accesar cualquier archivo de registros Btrieve, por lo que en particular sirve para accesar un archivo de registros de tipo "person". Como puede verse en el código, el primer argumento de xd_define() es la variable del preprocesador "dm" definida en "person.h" cuyo valor es, exactamente, "Btrieve". Este primer argumento, que se repite en la invocación a todas las operaciones de "genridx.h", es el que define al manejador de archivos en uso. Luego se incluye el nombre de la variable de descriptor de archivo "bFILE" que se desea definir: xd_define(dm, bFILE).

      Después de estas definiciones está el programa principal, que en C siempre se llama main(). Este programa primero imprime la posición en donde empieza y el largo de la primera llave, que es el campo "number" del archivo "person".

      El programa luego procede a abrir el archivo de personas llamado "PERSON.DAT", usando la variable de descriptor de archivo Btrieve "bFILE". Los dos últimos argumentos de xd_open() son el modo de apertura del archivo y el modo de sincronización multiusuario. En el caso de Btrieve tiene sentido únicamente el primer argumento, y el segundo es ignorado. Sin embargo, para otros manejadores de archivos es necesario definir ambos argumentos.

      El segundo argumento de xd_open() es el nombre del tipo al que se asociará el descriptor "bFILE". Es necesario que el programador haya definido en el archivo de encabezados "person.h" la variable NKEY_person, que debe ser el número de índices de un archivo de personas. Aunque esta información no es usada en esta implementación de la interfaz para Btrieve, si es necesario saber esta cantidad para implementar comodamente la interfaz para otros manejadores de archivos.

      Después de inicializar la variable "bFILE" mediante xd_open(), el programa borra el contenido anterior del archivo "PERSON.DAT" usando la operación xd_clear(dm, bFILE). A diferencia de algunos manejadores de archivos, en esta interfaz es necesario que un archivo ya esté abierto para que pueda se borrado.

      Como sucede con xd_clear(), todas las operaciones de acceso al archivo de personas necesitan como argumento a la variable "bFILE", la que contiene los campos necesarios para accesar un archivo Btrieve.

      El código de control de errores retornados por Btrieve se hace siguiendo la costumbre C de examinar un código de retorno mediante la instrucción if(), tomando la acción correctiva del caso. El programa "person.c" no es muy robusto, aunque siempre examina los códigos retornados por Btrieve.

      El siguiente bloque de código es el que cumple la función principal del programa "person.exe", que es crear el archivo "PERSON.DAT" junto a todos sus índices. Al diseñar "genridx.h" se decidió dejar por fuera la operación para crear archivos indexados simplemente porque cada manejador de archivos lo hace de una manera completamente diferente a los demás, y esta operación se realiza sólo una vez para cada archivo.

      El código que sigue incluye una invocación a xd_key_file() para cada una de las llaves del archivo. En el caso de Btrieve no es necesario abrir archivos de índices, pero otros manejadores de archivo sí lo requieren.

      Luego está un bloque de código que inserta en el archivo "PERSON.DAT" todos los registros definidos en el arreglo de personas llamado "people". Lo primero que se hace es poner la bandera de bloqueo (locking) para el archivo administrado por medio de "bFILE" en TRUE, para que todos los accesos a registros del archivo dejen el registro bloqueado. Luego, usando la operación xd_drn(), se obtiene el DRN de cada uno de los registros añadidos al archivo. Como en general los DRN's no son números consecutivos, es necesario almacenarlos en el arreglo drn[] para luego utilizarlos para hacer acceso directo a los registros por medio de xd_get(). También es necesario usar a la operación xd_unlock(), que desbloquea el registro recién insertado.

      Después de crear y llenar de registros el archivo, se procede a accesarlo de varias formas. La primera es la forma directa, para la que se usa el verbo xd_get() cuyos argumentos son un área en donde dejar el registro leido del archivo, y un DRN, además de los otros dos argumentos comunes a todos los verbos de acceso.

      Luego el programa accesa el archivo utilizando las dos llaves de acceso, llamadas KEY_name y KEY_number, nombres que deben haber sido definidos en el archivo de encabezado "person.h". El verbo xd_select() sirve para indicar cuál es el índice que se usará para recorrer el archivo. Cada uno de los índices es usado para recorrer el archivo hacia adelante y hacia atrás.

      También en el programa se ejercitan las funciones de acceso por llave xd_seek() y xd_find(). Para usar xd_seek() es necesario incluir como último argumento la llave de acceso, mientras que para usar xd_find() lo que se hace es copiar en una variable de tipo "person" los valores de los campos que forman la llave.

      La última operación del programa es cerrar el archivo usando el verbo xd_close(dm, bFILE). Como en el descriptor de archivos "bFILE" se almacena el código de retorno de cada una de las operaciones de archivos, es perfectamente válido usar el verbo xd_status() para averiguar el resultado de la última operación de acceso.

Uso del manejador de archivos Btrieve [<>] [\/] [/\]

      Sin la interfaz "genridx.h", el programa "person.c" del Listado 3 sería muy diferente. Cada acceso a un archivo Btrieve debe hacerse usando un protocolo preestablecido por medio de la función BTRV(), suplida en el paquete Btrieve, pues para usar Btrieve el programador debe definir, para cada archivo, tres tipos de objetos:
  1. El área para mantener el estado del archivo, llamado "position Block", con un tamaño de 128 bytes.
  2. Un área de tamaño suficiente como para almacenar la llave más grande de acceso a los registros.
  3. Invocación al sistema Btrieve mediante la función BTRV().

      Cuando se adquiere el programa Btrieve, Novell distribuye con el Btrieve varios archivos llamados ????BTRV.c, en donde las primeras cuatro letras dependen del compilador que el programador usa. Para el Turbo C, el archivo de interfaz con Btrieve se llama "turcBTRV.c", y en él se incluye el procedimiento BTRV(), que es la llave para invocar al Btrieve tanto en ambientes de redes como en DOS.

      En el caso específico de "turcBTRV.c", el autor es testigo de lo difícil que es llegar a entender cómo usarlo. Precisamente la poca calidad del código contenido en este archivo indujo al autor a escribir la interfaz genérica "genridx.h".

int BTRV(            /* Btrieve interface function */
    int      OP,           /* Código de operación  */
    char     POS_BLOCK[],  /* Bloque de posición   */
    char     DATA_BUF[],   /* Memoria para E/S     */
    int    * DATA_LEN,     /* Largo de DATA_BUF    */
    char     KEY_BUF[],    /* Llave de acceso      */
    int      KEY_NUM       /* Número de índice     */
);
Figura 4: Encabezado de BTRV()

      Cuando un programador necesita accesar un archivo, lo que hace es invocar la función BTRV() con los parámetros adecuados. Estos parámetros varían de acuerdo a la operación a realizar, pero cualquier operación sobre los archivos indizados debe hacerse por intermedio de BTRV(). Otros sistemas de archivo tienen una o varias funciones similares a BTRV(), aunque en general todos proveen las operaciones de "genridx.h". El encabezado de la función BTRV() se muestra en la Figura 4. El primero es el código de operación. El siguiente es un bloque de posición en el que Btrieve guarda algunos valores para procesar el archivo, como el DRN de los registros que anteceden y siguen al registro actual. El siguiente argumento debe ser un puntero al área de memoria en donde está el registro accesado. Luego sigue otro puntero a una variable de dos bytes de largo que contiene el tamaño del área de datos. El siguiente argumento es un puntero al área en que el programador ha construido la llave de acceso.

      Las llaves de un archivo Btrieve se numeran consecutivamente de cero en adelante. Cuando el programador necesita accesar el archivo por medio de una llave, entonces debe incluir el número de de la llave como sexto argumento al invocar a BTRV().

      El manual de Btrieve describe, en más de cuatrocientas páginas, el detalle de las peculiaridades de las casi cien diferentes operaciones que Btrieve ofrece. Para abrir y luego cerrar un archivo Btrieve, el programador debe escribir el siguiente código:

BTRV (0, position_block, NULL, 0, "PERSON.DAT", 0);
BTRV (1, position_block, NULL, 0,  NULL,        0);
Nótese lo difícil que es adivinar el significado de cada argumento de BTRV(), aún si se cuenta con el manual, y lo estéticamente negativo que es el código resultante. Esto contrasta enormemente con el método expuesto en este artículo:
#include "person.h"
ret = xd_open(dm, person, bFILE, "PERSON.DAT", MD_ACELERATED, 0);
ret = xd_close(dm, bFILE);

      A todas luces la segunda forma es más clara, elegante y completa. En la siguiente sección se discuten en detalle las ventajas principales de la interfaz "genridx.h".

Ventajas de usar "genridx.h" [<>] [\/] [/\]

      Una de las principales ventajas de usar "genridx.h" sobre el crudo método BTRV() es el poder usar nombres significativos para las operaciones y para sus argumentos. En el método BTRV() es muy difícil recordar, para un ser humano normal, que el código "0" es el que Btrieve usa para abrir un archivo, y que el "1" es el código para cerrarlo.

      Lo segundo que puede notarse es que para accesar un archivo en Btrieve, como es el caso con otros manejadores de archivos, es necesario definir una serie de variables de apoyo que sirven para mantener el estado del archivo Btrieve. En la Figura 4 se menciona a la variable POS_BLOCK, pero como se verá después es necesario definir muchas otras variable de apoyo. En una aplicación de mediano tamaño la proliferación de este tipo de variables complica mucho el programa, y obliga al programador a invertir grandes esfuerzos para controlar a Btrieve.

      Cuando el método para definir todos los objetos necesarios para usar el manejador de archivos no es automático el resultado puede ser un programa incorrecto debido no a un defecto de diseño, sino a uno de codificación. El autor en una ocasión perdió más de seis horas antes de darse cuenta de que no había definido la variable POS_BLOCK del tamaño adecuado, por lo que BTRV() retornaba resultados erróneos. Por eso es importante utilizar herramientas, como "genridx.h", que permitan simplificar el acceso a los archivos, pues de esa manera se evitan errores tontos y costosos.

      Para el lector que no tiene conocimiento alguno de cómo funciona el sistema de archivos Btrieve debe ser muy difícil saber (adivinar) cuáles son los parámetros de la función BTRV() para invocar la operación OPEN(). Para el que tiene el manual a la par de este escrito puede ser obvio, pero sólo si ya ha usado mucho el Btrieve y además tiene la paciencia de buscar en las páginas x-PP o y-QQ. Pero para la mayoría de los demás, es simplemente inconcevible el uso de un sistema de archivos que no es "amigable", y que requiere de una memoria de elefante y la delicadeza de un cirujano para utilizarse.

      Es fácil dar más argumentos para justificar la necesidad de las macros definidas en "btrieve.h" y "genridx.h", más hacerlo es gastar "pólvora en zopilotes". Es mejor pasar a discutir la forma en que están implementados los archivos "btrieve.h" y "btrieve.c", que forman la implementación Btrieve de la interfaz "genridx.h".

      El código en los archivos "btrieve.h" y "btrieve.c" permite transformar una invocación elegante, como las del programa "person.c", en el poco amigable llamado a BTRV() que Btrieve requiere. Mucho del código de estos archivos es definición de macros. La depuración de este código es difícil pues en general es difícil depurar programas que usan macros complejas como las de "btrieve.h". Como para crear la interfaz estándar ese trabajo debe hacerse una sóla vez, el resultado bien vale el sudor que cuesta.

      Definitivamente la programación Btrieve por medio de "genridx.h" es mucho más sencilla que usar BTRV() solo.

El encabezado "btrieve.h" [<>] [\/] [/\]

      "btrieve.h" nació como una concha alrededor de "turcBTRV.c". Paulatinamente se transformó en "genridx.h" y por último pasó a partirse en dos archivos: "btrieve.h" y "genridx.h".

      Al incluir en un programa el archivo de encabezado "btrieve.h" el programador define todas las macros que se invocan por medio de "genridx.h", como Btrieve_define(), Btrieve_select(), etc. También declara diversas funciones de apoyo, y define muchas de las constantes relevantes al uso de archivos Btrieve.

      La mayor parte de las contantes definidas en "btrieve.h" son códigos de estado o códigos de error. Por ejemplo, la variable KEY_NOT_FOUND (y por ende Btrieve_KEY_NOT_FOUND) está definida como el valor "4", que es el código que BTRV() retorna cuando busca una llave en un archivo y no la encuentra.

      Para usar Btrieve es necesario definir tres tipos de constantes: códigos de operación, códigos para definir los campos y llaves de los archivos, y códigos de error.

      Los códigos de operación definidos en "btrieve.h" comienzan todos con el prefijo "BT_", como BT_OPEN que es el código de operación que BTRV() debe recibir como primer argumento para abrir un archivo. Los códigos para definir archivos empiezan con "XT_", como en XT_MONEY si son códigos extendidos (que es un concepto definido en el manual de uso del paquete) o tienen un nombre significativo en mayúsculas, como BINARY. Por último, los nombres de los códigos de error son muy largos, y en la mayoría de los casos son muy significativos (INVALID_FILE_NAME).

      La más importante de las funciones declaradas en "btrieve.h" es Btrieve_generic(), la que se encarga de llamar a BTRV() con la salvedad de que usa una estructura de tipo Btrieve_access, que es el tipo de una variable descriptora de archivos. En efecto, la macro xd_declare(Btrieve, bFILE) se traduce en definir e inicializar "bFILE" como una variable de tipo Btrieve_access en la que se guardan los parámetros de acceso necesarios para usar Btrieve en un archivo. La función Btrieve_generic() deja en esta estructura el código de error retornado por BTRV().

      La única ineficiencia en tiempo de ejecución que el uso de estas macros representa es el llamado adicional a Btrieve_generic(), que en la mayoría de los casos en despreciable. Lo mismo no puede decirse, sin embargo, del tiempo de compilación, pues después de todo el tamaño del archivo "btrieve.h" es un poco grande, y la mayoría de los compiladores se gastan sus momentos para degullirlo.

#define dm Btrieve /* Parametriza el manejador de archivos */

/* invocaciones equivalentes de la macro */

xd_find(Btrieve, bFILE, d)
xd_find(dm,      bFILE, d)


/* resultado de expander la macro */

(
    Btrieve_build_key(
        (bFILE).stat,
        (bFILE).key_number,
        (bFILE).key_buffer,
        (char*)d
    ),
    Btrieve_generic(BT_GET_EQUAL, &(bFILE), d)
)
Figura 5: Expansión de xd_find(Btrieve, bFILE, d)

      La Figura 5 es el resultado de expandir la macro xd_find(Btrieve, bFILE, d), que resulta en una invocación a Btrieve_generic(). El nombre del manejador de archivos en uso, que es "Btrieve" en este caso, se usa en la implementación de todas las operaciones de acceso: Btrieve_generic(), Btrieve_access, etc.

      "btrieve.h" fue implementado incialmente para el compilador Turbo C++ v1.0, por lo que incluye código para que la interfaz pueda ser usada en C o en C++, y en cualquiera de los modelos de memoria que soporta ese compilador. Esta generalidad obliga a usar compilación condicional para el manejo de cada modelo de memoria lo que a veces complica un poco las cosas.

Implementación de "btrieve.c" [<>] [\/] [/\]

      En "btrieve.c" se encuentra el código que implementa cada una de las rutinas definidas en "btrieve.h". Además, también está la tabla de mensajes de error usada por Btrieve_error()[4].

      Tal vez la parte más oscura en "btrieve.c" sea el código que está alrededor de la directiva #include "turcbtrv.c", archivo que contiene el código para invocar a Btrieve desde un programa escrito en Turbo C. El archivo "turcbtrv.c" está muy mal programado, pues asume que la versión del compilador a usar es de la etapa paleolítica de la computación. Para lograr compilar este código en las versiones v2.0 y posteriores no queda más que engañar un poco al compilador, pues de lo contrario se obtienen una veintena de errores de compilación. Por eso el código que se encuentra alrededor del #include "turcbtrv.c" declara algunas funciones del archivo de encabezado <dos.h> como funciones que reciben argumentos de tipo "void*".

      Al probar la interfaz con Turbo C++ versión 1.0, y cuando se usó el compilador de C++ en lugar del de C, en muchas ocasiones el ligador de eslabonamiento retornó un error de FIXUP OVERFLOW que fue imposible eliminar. Pareciera que este problema sólo existe con la versión 1.0, pues en las versiones de Turbo C++ posteriores a esa este problema está resuelto: la implementación actual de "btrieve.c" no presenta estos problemas.

      Cada archivo Btrieve se accesa usando un descriptor de archivo, de tipo "Btrieve_access". Los campos de este archivo contienen punteros a varias áreas de memoria que se asignan dinámicamente en la operación Btrieve_open_file(), y que se liberan con Btrieve_close_file(). Unicamente el campo "type_name" no se asigna en la memoria dinámica, pues la macro Btrieve_open() genera la tira con el nombre del tipo del archivo que se ha abierto. La rutina Btrieve_clear_file() ha debibo programarse usando una lógica especial, pues Btrieve no incluye una operación que permita borrar todos los registros de un archivo sin borrar el archivo completo.

      Las rutina Btrieve_longest_key() determina el tamaño más grande posible para cada una de las llaves del archivo. Para esto usa la información sobre la posición de cada llave en el archivo que está almacenada en un área de memoria dinámica a la que apunta el campo "stat". El valor retornado por esta función sirve para asignar una área de memoria a la que apunta el campo "key_buffer", en donde se almacena la llave de acceso al archivo. La función build_user_key() se encarga de construir, en el área denotada "key_buffer", la llave del usuario con base a los datos almacenados en un registro del usuario. Esta función es necesaria para implementar el Btrieve_seek().

      Al programar esta interfaz para otro manejador de archivos será necesario que el programador supla, de alguna manera, una estructura de datos que describa cómo está formada cada llave. Esta es la estructura que una función similar a build_user_key() necesita para contruir la llave en xd_seek(). En el caso de Btrieve, la estructura retornada por la operación STAT() principalmente sirve para describir las llaves del archivo.

      La otra importante función es Btrieve_generic(), la que se encarga de llamar a BTRV(), y guardar el código de retorno. Además, en los casos en que debe usarse bloqueo de registros, esta función se encarga de ajustar el código de operación para BTRV().

      La implementación "btrieve.c" es bastante simple, pues la mayor parte del que se presenta en este archivo descansa mucho en las macros definidas en "btrieve.h". Esto obedece un poco a que la función BTRV() es muy general. Para otros manejadores de archivos el archivo de implementación análogo al "btrieve.c" será mucho más complicado.

Uso de dos manejadores de archivos a la vez [<>] [\/] [/\]

      Es perfectamente factible usar dos manejadores de archivos al mismo tiempo, siempre y cuando entre ellos no se den incompatibilidades inherentes. Para lograrlo, basta que en cada archivo de encabezado para un archivo indexado se incluya la directiva #define que nombre al manejador de archivos.

      Como ejemplo, supóngase que es necesario usar el archivo descrito en "person.h" y en "account.h". Si el primero es un archivo Btrieve entonces la siguiente línea de código debe aparecer en "person.h":
      #define dm1 Btrieve

      Como el segundo es un archivo DDMPC, entonces en el archivo de encabezado "account.h" debe aparecer la línea:
      #define dm2 DDMPC

      Luego basta usar las operaciones de accesso definidas en "genridx.h" en un caso con la variable de compilación "dm1" y en el otro con "dm2", según corresponda.

Implementación de otras interfaces con base a "genridx.h" [<>] [\/] [/\]

      Por no utilizar demasiado espacio en este artículo se describe únicamente la implementación de "genridx.h" relevante a Btrieve. Sin embargo, el lector puede fácilmente hacer otras implementaciones. Para esto es conveniente seguir los siguientes pasos:
  1. Estudiar en detalle la implementación para Btrieve, con el objeto de entender bien el significado de cada uno de los verbos definidos en "genridx.h".
  2. Estudiar en detalle el manual de uso del nuevo manejador de archivos, para entender sus cualidades y restricciones respecto de "genridx.h".
  3. Escoger una estructura de datos adecuada para implementar el tipo descriptor de archivos. En muchos casos el manejador de archivos definirá sus propios tipos, por lo que en general esta labor no es tan pesada como lo fue al implementar "btrieve.c".
  4. Implementar los verbos de acceso. Aquellos que no sea posible implementar es necesario sustituirlos por un error en tiempo de ejecución o de compilación.

Conclusión [<>] [\/] [/\]

      Cada vez es más importante lograr que los programas puedan correr en diversas plataformas [6]. Aunque el enfoque aquí descrito no es único ([2], [5]), si es una alternativa viable en muchos casos.

      Los programas C que usan la interfaz definida en "genridx.h" pueden ser cambiados, con relativa facilidad, para que usen un nuevo manejador de archivos. Por ejemplo, es posible escribir un programa usando Btrieve, y luego cambiarlo para que la versión definitiva use Code Base. Esta interfaz depende mucho del preprocesador del lenguaje C, el que se usa para parametrizar el acceso a archivos indizados. Más aún, "genridx.h" es una herramienta que permite la coexistencia de más de una manejador de archivos en el mismo programa.

      Aunque en general no es muy fácil implementar la interfaz para un manejador de archivos específico, este trabajo debe hacerse una sóla vez. La ventaja de usar "genridx.h" es que se libera a muchos programadores del aprendizadje de los detalles particulares de diversos manejadores de archivos, lo que a la larga abarata el costo de producción de sistemas.

Agradecimiento [<>] [\/] [/\]

      Al escribir este artículo, el autor ha recibido muchas ideas de don Carlos Loría Beeche, presidente de Loría Beeche Asociados. Fue don Carlos quien propició la madurez de las ideas expuestas en este artículo. También ha servido como conejillo de indias para depurar el código de "btrieve.h", "btrieve.c" y "genridx.h".


Notas de pie de página [<>] [\/] [/\]

[*] Esta investigación se realizó dentro del proyecto de investigación 329-89-019 "Estudios en la tecnología de programación por objetos y C++", inscrito ante la Vicerrectoría de Investigación de la Universidad de Costa Rica. La Escuela de Ciencias de la Computación e Informática también ha portado fondos para realizar este trabajo.
[1] En opinión del autor, la rebeldía al uso de encabezados para describir la estructura de archivos de datos debe combatirse con una reducción del sueldo del programador, proporcional a la intensidad de la rebelión.
[2] Para evitar usar el archivo person.c se puede crear una macro en el encabezado person.h (con "h"), para inicializar las varibles de tipo person; esto abulta el código generado. En esto cada programador escoge su propio rumbo.
[3] En conjunto, las referencias [4] y [9] representan una manera efectiva y sencilla de dominar los detalles del uso de Btrieve.
[4] Por razones de espacio el listado completo del archivo "btrieve.c" no se incluye en el texto del artículo, pero está disponible en Internet en:
      http://www.uaca.ac.cr/actas/1994nov/genridx.zip


Bibliografía [<>] [\/] [/\]

[1] Borland International: Borland C++ v3.1 Programmer's Guide, Borland International, California (U.S.A.), 1992.
[2] Brown, J. Randolph: Cross-platform Database Development, Strategies for FoxPro Developers, Dr. Dobb's Journal, No.215, Junio 1994.
[3] Butterfield, Timothy: File Conversion using C++ Templates, Dr. Dobb's Journal, No.198, Marzo 1994.
[4] Chang, Chao Lin: C++ class library for Btrieve file manipulation, disponible en Internet en ftp:// oak.oakland.edu /SimTel/msdos/cpluspls/ bfast11.zip, 1992.
[5] Dowgiallo, Edward: Database Interoperability and Application Transportability, Dr. Dobb's Journal, No.208, Diciembre 1993.
[6] Duntemann, Jeff: Structured Programming: Action at distance, Dr. Dobb's Journal, No.198, Marzo 1993.
[7] Murph, Thomas: Hiding ISAM Function Libraries with OOP, The C Users Journal, Vol.11, No.1, Enero 1993.
[8] Pruett, Mark: Mixing C with SQL, The C Users Journal, Vol.9, No.6, Junio 1991.
[9] Reilly, Douglas: Inside Btrieve Files, Dr. Dobb's Journal, No.198, Marzo 1993.
[10] Reilly, Douglas: Accessing NetWare SQL Files without NetWare SQL, Dr. Dobb's Journal, No.204, Septiembre 1993.
[11] Ware, John: Mixing C with Informix, The C Users Journal, Vol.9, No.6, Junio 1991.
[12] Winchell, Jeff: The Limits of PC Databases, DBMS, Vol.7, No.7, Junio 1994.

Listado 1: person.h [<>] [\/] [/\]

/* @(#)    person.h       Copyright 1994 Adolfo Di Mare      */

/*************************************************************/
/*   Header file to acces the person indexed data file       */
/*************************************************************/

#ifndef _person_h
#define _person_h   /* avoid multiple inclusion   */


/* Parametrize the access method */

#define dm Btrieve  /* use Btrieve record manager */

/* "dm", and not "Btrieve", will be the first argument in
   every invocation to the operations defined in the 
   "genridx.h" interface:
   - Changing record managers can be accomplished by 
     changing the value for "dm", and recompiling.
*/

#include   "btrieve.h"

/* constanst to avoid using "magic numbers" */

#define SZ_number   ( 8+1)  /* force NON alignment */
#define SZ_name     (15+1)
#define SZ_birth    ( 8+1)

/* constants to define each file index */

#define KEY_number    0  /* first key  */
#define KEY_name      1  /* second key */
#define NKEY_person   2  /* number of indexes for this file */

typedef struct {                   
    char  number[SZ_number];          /* id number     */
    float amount;                     /* amount owed   */
    char  name[SZ_name];              /* person's name */
    char  sex;                        /* only safe     */
    char  birth[SZ_birth];            /* date          */
} person;

void init_person(person *);

#endif /* _person_h */

Listado 2: genridx.h [<>] [\/] [/\]

/* @(#)   genridx.h      Copyright 1990 Adolfo Di Mare       */
/*                                                           */
/*        Macros para parametrizar el metodo de acceso       */

#ifndef _genrixd_h
#define _genrixd_h

#include <stddef.h> /* offsetof() && NULL */

/* Types for different return codes */
typedef  unsigned long drn_t;
typedef  short         xdret_t;

#ifndef fsizeof
#define fsizeof(type, field) sizeof( ((type*)0)->field )
#endif

#define _xd_P2(z,y) _xd_Paste2_i(z, y) /* double indirection */
#define _xd_Paste2_i(z,y)  z##y      /* as suggested by ANSI */

#define field_kpos(type, field) offsetof(type, field)
#define field_size(type, field) fsizeof(type, field)

#define xd_max(a,b) ((a) > (b) ? (a) : (b))
#define xd_min(a,b) ((a) < (b) ? (a) : (b))

/* macros used to define the position and size */
/* of a record field in an indexed file        */

#define xd_field_kpos(xd, t, f)     _xd_P2(xd, _field_kpos)(t, f)
#define xd_field_size(xd, t, f)     _xd_P2(xd, _field_size)(t, f)

/* Most usual return codes */
#define xd_OK(xd)                   _xd_P2(xd, _OK)
#define xd_NO_MEM(xd)               _xd_P2(xd, _NO_MEM)
#define xd_CRASH(xd)                _xd_P2(xd, _CRASH)
#define xd_NOT_FOUND(xd)            _xd_P2(xd, _NOT_FOUND)
#define xd_DUPLICATE(xd)            _xd_P2(xd, _DUPLICATE)
#define xd_LOCKED(xd)               _xd_P2(xd, _LOCKED)
#define xd_BOF(xd)                  _xd_P2(xd, _BOF)
#define xd_EOF(xd)                  _xd_P2(xd, _EOF)
#define xd_NOT_OPEN(xd)             _xd_P2(xd, _NOT_OPEN)
#define xd_DATA_LENGTH(xd)          _xd_P2(xd, _DATA_LENGTH)

#define xd_MAX_FILE_NAME_SZ(xd)     _xd_P2(xd, _MAX_FILE_NAME_SZ)
#define xd_error(   xd, err)        _xd_P2(xd, _error)  (err)
#define xd_file(    xd, f)          _xd_P2(xd, _file)   (f)
#define xd_type(    xd, f)          _xd_P2(xd, _type)   (f)
#define xd_status(  xd, f)          _xd_P2(xd, _status) (f)

/* These macros are used to define global variables where */
/* all the status information for file "t" is stored      */
#define xd_declare( xd)             _xd_P2(xd, _declare)
#define xd_define(  xd, f)          _xd_P2(xd, _define)  (f)

/* File access verbs */
#define xd_bind(    xd, f, ptr)     _xd_P2(xd, _bind)  (  f, ptr)
#define xd_bound(   xd, f)          _xd_P2(xd, _bound) (  f)
#define xd_is_open( xd, f)          _xd_P2(xd, _is_open) (f)

#define xd_open(    xd, t,f,n, m,s) _xd_P2(xd, \
          _open)      ( t,f,n, m,s)

#define xd_key_file(xd, k,f,n, m,s) _xd_P2(xd, \
          _key_file)  ( k,f,n, m,s)

#define xd_reindex( xd, f,k)        _xd_P2(xd, _reindex) (f,k)
#define xd_close(   xd, f)          _xd_P2(xd, _close) (f)
#define xd_clear(   xd, f)          _xd_P2(xd, _clear) (f)

#define xd_find(    xd, f)          _xd_P2(xd, _find)  (f)
#define xd_seek(    xd, f, key)     _xd_P2(xd, _seek)  (f, key)
#define xd_search(  xd, f)          _xd_P2(xd, _search)(f)

#define xd_first(   xd, f)          _xd_P2(xd, _first) (f)
#define xd_last(    xd, f)          _xd_P2(xd, _last)  (f)
#define xd_next(    xd, f)          _xd_P2(xd, _next)  (f)
#define xd_prev(    xd, f)          _xd_P2(xd, _prev)  (f)

#define xd_get(     xd, f, drn)     _xd_P2(xd, _get)   (f, drn)
#define xd_drn(     xd, f, drn)     _xd_P2(xd, _drn)   (f, drn)

#define xd_ins(     xd, f)          _xd_P2(xd, _ins)   (f)
#define xd_mod(     xd, f)          _xd_P2(xd, _mod)   (f)
#define xd_del(     xd, f)          _xd_P2(xd, _del)   (f)

/* Index && direct access selection */
#define xd_select(   xd, f, key)    _xd_P2(xd, _select) (f, key)
/* Force drn access: xd_select(xd, f, DRN_ACCESS) */
#define DRN_ACCESS (-1)

/* Locking directives */
#define xd_use_locks(xd, f, bool) _xd_P2(xd, _use_locks)(f, bool)
#define xd_locking(  xd, f)       _xd_P2(xd, _locking)  (f)
#define xd_unlock(   xd, f, drn)  _xd_P2(xd, _unlock)   (f, drn)

/* file statistics */
#define xd_rec_count(xd, f)       _xd_P2(xd, _rec_count) (f)

/* Macro to conditionally execute a series of file operations */
#define xd_try(xd, f, operation)   \
   (f).status =                    \
       (  xd_OK(xd) == (f).status  \
             ? (operation)         \
             : ((f).status)        \
       )

#ifndef TRUE
    #define TRUE   1
    #define FALSE  0
#endif

#if defined(__TURBOC__)
    #if (sizeof(unsigned long) < 4)
        #error drn_t is not long enough
    #endif
#endif

#endif  /* _genridx_h */

Listado 3: person.c [<>] [\/] [/\]

/* @(#)    person.c           Copyright 1994 Adolfo Di Mare    */

/***************************************************************/
/* person.c: Maintenance of the "person@ Btrieve record file   */
/*       Creates, inserts, modifies, reads and deletes records */
/***************************************************************/

#include "person.h"
#include <string.h>


void init_person(person *p) {
/* Construct "person" */

    memset(p, 0, sizeof(person));
};

#define CREATE
#ifdef  CREATE

#include <stdio.h>
#include <stdlib.h>

extern unsigned _stklen = 32000u; /* Required by Borland C++ */

/* Btrieve file specifications for the "person" file */

struct {
    btr_filespec   f;
    btr_keyspec    r[3];
} file_description =
{
    {  /* btr_filespec: file definition */
       sizeof(person), /* record length                      */
       512,            /* page size                          */
       NKEY_person,    /* number of indexes                  */
       0,              /* total active (non deleted) records */
       0,              /* bit flag vector: file attributes   */
       {0,0},          /* reserved                           */
       10,        /* # of pages to pre-allocate for the file */
    },

    {  /* btr_keyspec: key && key-segment definition */

       {      /* KEY_number */
       xd_field_kpos(dm,person,number), /* start pos of key arg */
       xd_field_size(dm,person,number), /* key segment length   */
       0,           /* bit flag vector: key attributes   */
       0,           /* unique key count (no dups, nulls) */
       BT_ZSTRING,  /* bit flags vector: data type       */
       0,           /* value for null key (0 or space)   */
       {0,0,0,0},   /* reserved                          */
       },


       {      /* KEY_name */
       xd_field_kpos(dm, person, name), /* start pos of key arg */
       xd_field_size(dm, person, name), /* key segment length   */
       BT_SEGMENTED,/* bit flag vector: key attributes   */
       0,           /* unique key count (no dups, nulls) */
       BT_ZSTRING,  /* bit flags vector: data type       */
       0,           /* value for null key (0 or space)   */
       {0,0,0,0},   /* reserved                          */
       },
       {
       xd_field_kpos(dm,person,number), /* start pos of key arg */
       xd_field_size(dm,person,number), /* key segment length   */
       0,           /* bit flag vector: key attributes   */
       0,           /* unique key count (no dups, nulls) */
       BT_ZSTRING,  /* bit flags vector: data type       */
       0,           /* value for null key (0 or space)   */
       {0,0,0,0},   /* reserved                          */
       },
    },
};  /* <====  global variable */

#define DIM(vec) (sizeof(vec)/sizeof(*vec))

person people [] = {
    { "11111111",   1256.00,   "David",    'M',  "270762", },
    { "22222222",    356.00,   "Ricardo",  'M',  "310189", },
    { "33333333",    100.00,   "Maria",    'F',  "011273", },
    { "44444444",   1103.25,   "Lorena",   'F',  "230475", },
    { "55555555",   4389.50,   "Joaquin",  'M',  "010160", },
    { "12345678",     89.50,   "Max",      'M',  "010190", },
    { "99999999",    122.35,   "Joaquin",  'M',  "030387", },
};

person buff;

void print_person (const person *);
int  check_error  (xd_declare(dm) *pfile);

void main(void) {
    int i;
    drn_t drn[DIM(people)];
    xd_define(dm, bFILE);

    unsigned int key_pos = xd_field_kpos(dm, person, number);
    unsigned int key_len = xd_field_size(dm, person, number);

    printf("\nKey_pos = %d Key_len = %d\n", key_pos, key_len);

    #if dm == Btrieve
        Btrieve_reset();
    #endif




    /* if the file exists, it overwrites it */
    bFILE.status = xd_OK(dm);
    xd_try(dm, bFILE,
        xd_open(dm, person, bFILE,
                            "PERSON.DAT", BT_ACELERATED, +++));

    /* xd_try(dm, bFILE, op) executes "op"  only if the "status" 
       field withing "bFILE" has value equal to xd_OK(dm). 
       - This is a neat trick to delay evaluation of the
         error return code.
    */
       
    if (xd_OK(dm) == bFILE.status) {
        xd_try(dm, bFILE, xd_reindex(dm, bFILE, KEY_person));
        xd_try(dm, bFILE, xd_clear(dm, bFILE));
        xd_try(dm, bFILE, xd_close(dm, bFILE));
        check_error(&bFILE);
    }

    /* create the indexed file */
    i = sizeof(file_description);
    bFILE.status =
        BTRV(BT_CREATE,
            (char*)   bFILE.pos_block,
            (char*) & file_description,
            (int*)  & i,
            (char*)   "PERSON.DAT",
            0);
    check_error(&bFILE);

    /* open the file and its indexes */
    bFILE.status = xd_OK(dm);
    xd_try(dm, bFILE,
        xd_open(dm, person, bFILE,
                        "PERSON.DAT", BT_ACELERATED, +++));
    xd_try(dm, bFILE,
        xd_key_file(dm, KEY_number, bFILE,
                        "PERSON.DAT", BT_ACELERATED, +++));
    xd_try(dm, bFILE,
        xd_key_file(dm, KEY_name,   bFILE,
                        "PERSON.DAT", BT_ACELERATED, +++));

    check_error(&bFILE);    /* check cascade of operations */

    printf ("\n\n Loading the file");
    xd_use_locks(dm, bFILE, TRUE);
    for (i=0; xd_OK(dm) == bFILE.status && i<DIM(people); i++) {
        print_person(&people[i]);
        bFILE.status = xd_OK(dm);
        xd_try(dm, bFILE, xd_bind(dm, bFILE, & people[i]));
        xd_try(dm, bFILE, xd_ins(dm,    bFILE));
        xd_try(dm, bFILE, xd_drn(dm,    bFILE, & drn[i]));
        xd_try(dm, bFILE, xd_unlock(dm, bFILE,   drn[i]));
        check_error(&bFILE);
    }
    bFILE.status = xd_OK(dm);  /* redundant, but OK */
    xd_use_locks(dm, bFILE, FALSE);
    xd_try(dm, bFILE, xd_bind(dm, bFILE, &buff));
    check_error(&bFILE);

    printf ("\n\n File accessed direct");
    xd_select(dm, bFILE, DRN_ACCESS);

    i = 0;
    bFILE.status = xd_get(dm, bFILE, drn[i]);
    while ( xd_OK(dm) == bFILE.status && i < DIM(people) ) {
        print_person(&buff);
        i++;
        bFILE.status = xd_get(dm, bFILE, drn[i]);
    }

    printf ("\n\n File ordered by name (FORWARD)");
    xd_select(dm, bFILE, KEY_name);
    bFILE.status = xd_first(dm, bFILE);
    while ( xd_OK(dm) == bFILE.status ) {
        print_person(&buff);
        bFILE.status = xd_next(dm, bFILE);
    }

    printf ("\n\n File ordered by name (BACKWARDS)");
    xd_select(dm, bFILE, KEY_name);
    bFILE.status = xd_last(dm, bFILE);
    while ( xd_OK(dm) == bFILE.status ) {
        print_person(&buff);
        bFILE.status = xd_prev(dm, bFILE);
    }

    printf ("\n\n File ordered by number-name (FORWARD)");
    xd_select(dm, bFILE, KEY_number);
    bFILE.status = xd_first(dm, bFILE);
    while ( xd_OK(dm) == bFILE.status ) {
        print_person(&buff);
        bFILE.status = xd_next(dm, bFILE);
    }

    printf ("\n\n File ordered by number-name (BACKWARDS)");
    xd_select(dm, bFILE, KEY_number);
    bFILE.status = xd_last(dm, bFILE);
    while ( xd_OK(dm) == bFILE.status ) {
        print_person(&buff);
        bFILE.status = xd_prev(dm, bFILE);
    }

    printf ("\n\n Access by KEY_number");   /* provide the key */
    xd_select(dm, bFILE, KEY_number);       /* directly        */
    xd_seek(dm, bFILE, "55555555");
    check_error(&bFILE);
    print_person(&buff);


    printf ("\n\n Access by KEY_name");
    xd_select(dm, bFILE, KEY_name);
    strcpy(buff.number, "44444444"); /* store key components */
    strcpy(buff.name, "Lorena");     /* in the data buffer   */

    xd_find(dm, bFILE);
    check_error(&bFILE);
    print_person(&buff);

    printf(
        "\n\n Changing amount to 5.0 by number-name (FORWARD)");
    xd_select(dm, bFILE, KEY_number);
    bFILE.status = xd_first(dm, bFILE);
    while ( xd_OK(dm) == bFILE.status ) {
        buff.amount = 5.0;
        xd_mod(dm, bFILE);
        print_person(&buff);
        bFILE.status = xd_next(dm, bFILE);
    }
    if (xd_EOF(dm) != bFILE.status) {
        check_error(&bFILE);
    }

    printf(
        "\n\n File ordered by physical position (FORWARD)");
    xd_select(dm, bFILE, DRN_ACCESS);
    bFILE.status = xd_first(dm, bFILE);
    check_error(&bFILE);
    while ( xd_OK(dm) == bFILE.status ) {
        print_person(&buff);
        bFILE.status = xd_next(dm, bFILE);
    }

    printf("\n\n File ordered by physical position (BACKWARD)");
    xd_select(dm, bFILE, DRN_ACCESS);
    bFILE.status = xd_last(dm, bFILE);
    check_error(&bFILE);
    while ( xd_OK(dm) == bFILE.status ) {
        print_person(&buff);
        bFILE.status = xd_prev(dm, bFILE);
    }
    printf(
        "\n\n [%ld] records processed", xd_rec_count(dm,bFILE));

    printf(
        "\n\n Deleting al records by number-name (BACKWARDS)");
    xd_select(dm, bFILE, KEY_number);
    bFILE.status = xd_last(dm, bFILE);
    while ( xd_OK(dm) == bFILE.status ) {
        print_person(&buff);
        bFILE.status = xd_del(dm, bFILE);
        check_error(&bFILE);
        bFILE.status = xd_last(dm, bFILE);
    }

    if (xd_EOF(dm) != bFILE.status) {
        check_error(&bFILE);
    }

    /* close the file */
    xd_close(dm, bFILE);
    check_error(&bFILE);

    printf ("\n End of process\n");
    exit (0);
}

void print_person(const person *bFILE) {
/* "Pretty" prints a person record.
*/
    printf ("\n N(%s)  Name: %-10.10s Sx(%c) Am(%7.2f) F(%s)",
        bFILE->number, bFILE->name,   bFILE->sex,
        bFILE->amount, bFILE->birth
    );
}

int check_error(xd_declare(dm) *bFILE) {
/* Checks the error status code in bFILE, and bombs
   when it's no good.
*/
    if (xd_OK(dm) != bFILE->status) {
        printf(
            "\nError code(%d) \"%s\" [%s:%s]\n",
            bFILE->status,
            xd_error(dm, bFILE->status),
            xd_type(dm, *bFILE),
            xd_file(dm, *bFILE)
        );
        xd_close(dm, *bFILE);
        exit(bFILE->status);
    }
    return FALSE;
} /* check_error */

#endif /* CREATE */

/* End of file person.c */


Indice [<>] [\/] [/\]

[-] Resumen
[-] Introducción
[-] La programación en C
[-] Definición de archivos
[-] Uso de encabezados para describir archivos
[-] El macro preprocesador de C
[-] Interfaz genérica "genridx.h"
[-] Verbos de acceso a archivos indizados
[-] La implementación de "person.c"
[-] Uso del manejador de archivos Btrieve
[-] Ventajas de usar "genridx.h"
[-] El encabezado "btrieve.h"
[-] Implementación de "btrieve.c"
[-] Uso de dos manejadores de archivos a la vez
[-] Implementación de otras interfaces con base a "genridx.h"
[-] Conclusión
[-] Agradecimiento

[-] Listado 1: person.h
[-] Listado 2: genridx.h
[-] Listado 3: person.c

Notas de pie de página
Bibliografía
Indice
Acerca del autor
Acerca de este documento
[/\] Principio [<>] Indice [\/] Final


Acerca del autor [<>] [\/] [/\]

Adolfo Di Mare: Consiliario Académico, Maestro Tutor del Collegium Stvdivm Generale. Imparte la Cátedra de Programación de Computadoras en la Universidad de Costa Rica [UCR]. Master en Ciencias en la Universidad de California, Los Angeles [UCLA]. Profesor Asociado U.C.R. Catedrático de la UACA.

[mailto] Adolfo Di Mare <adolfo@di-mare.com>


Acerca de este documento [<>] [\/] [/\]


Referencia: Di Mare, Adolfo: "genridx.h" Una interfaz uniforme para el uso de archivos indizados en C, Revista Acta Académica, Universidad Autónoma de Centro América, Número 15, pp [35­58], ISSN 1017­7507, Noviembre 1994.
Internet: http://www.uaca.ac.cr/actas/1994nov/genridx.htm
http://www.uaca.ac.cr/actas/1994nov/genridx.zip

http://www.di-mare.com/adolfo/p/genridx.htm
http://www.di-mare.com/adolfo/p/src/genridx.zip
Autor: Adolfo Di Mare <adolfo@di-mare.com>
Contacto: Apdo 7637-1000, San José Costa Rica
Tel: (506) 234-0701       Fax: (506) 224-0391
Revisión: UACA, Enero 1998
Visitantes:

ACTA ACADEMICA no pone como requisito que los artículos sean inéditos, ni adquiere la propiedad de ellos. Pueden ser citados (pero no reproducidos) libremente, siempre que se indique la fuente. Para reproducir el artículo se necesita permiso del (los) autor(es). Cada autor da permiso para que Acta Académica publique en la Internet la versión electrónica de su artículo. Los autores deben corregir las artes de su artículo.
ACTA ACADEMICA neither requires for articles to be unpublished, nor acquires their property. They can be quoted (but not reproduced) freely, but always indicating the source. Permisson from the author(s) is required to reproduce an article. Each author gives permission to Acta Académica to publish in the Internet the electronic version of his/her article. Authors must correct the arts of their article.


[mailto] ACTA ACADEMICA, UACA.

Copyright © 1994 Adolfo Di Mare
Derechos de autor reservados © 1994
[home] [<>] [/\]