Acta Académica Universidad Autónoma de Centro América |
|
genridx.h
"
Adolfo Di Mare
|
"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. |
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
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.
+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 */ |
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 */ |
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.
.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:
person.h
"
usando la directiva #include
del preprocesador del
lenguaje C:
- #include "person.h"
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
*/
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:
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.
#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.
genridx.h
"
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 */ |
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.
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
"
person.c
"
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.
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:
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 */ ); |
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:
Nótese lo difícil que es adivinar el significado de cada argumento deBTRV (0, position_block, NULL, 0, "PERSON.DAT", 0); BTRV (1, position_block, NULL, 0, NULL, 0);
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
".
genridx.h
"
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.
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) ) |
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.
btrieve.c
"
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.
#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.
genridx.h
"
genridx.h
"
relevante a Btrieve. Sin embargo, el lector puede
fácilmente hacer otras implementaciones. Para esto es
conveniente seguir los siguientes pasos:
genridx.h
".genridx.h
".btrieve.c
".
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.
btrieve.h
", "btrieve.c
" y
"genridx.h
".
[*] | 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
|
[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.
|
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 */
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 */
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 */
[-] | 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
|
Adolfo Di Mare <adolfo@di-mare.com>
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 [3558],
ISSN 10177507, Noviembre 1994.
|
Internet: |
http://www.uaca.ac.cr/actas/1994nov/genridx.htm
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.
|
ACTA ACADEMICA,
UACA.
Copyright © 1994 Adolfo Di Mare
|