|
Programación de librerías
ILBM 2ª parte
Por Vicente Santos
CAPITULO
2.
Continuando nuestro interesante
culebrón, en este capítulo vamos a crear el programa principal
y a generar "ilbm.library".
Lo primero que vamos a escribir
son unos ficheros de cabecera (esto se está convirtiendo en costumbre...),
en los que declararemos una serie de macros y estructuras necesarias
para el ensamblaje del programa.
FICHEROS DE CABECERA
El primero
es "macroslib.i" y contiene, pues eso, unas macros para simplificar
el desarrollo del código. Los más listillos ya se habrán
dado cuenta que se trata de una versión resumida del fichero
"asmsupp.i" original de Commodore, cuyo listado se puede encontrar
en el volumen Includes & Autodocs de los manuales oficiales. Este
fichero incluye macros para desarrollo no sólo de librerías
sino también de dispositivos (devices), bastante relacionados
con las run-time, pero como estos no nos interesan he decidido, para
no liar la manta, dejarlo reducido a las imprecindibles.
El segundo, "ilbmbase.i",
es importantísimo pues en el se declara la estructura de la Base
de la librería y su nombre propio.
PROGRAMA PRINCIPAL
Lo tienen en el módulo
"ilbm.s". Como creo que ya he dicho en algún otro sitio,
el programa está escrito con HiSoft Devpac 2, de modo que utiliza
una directiva no admitida por otros ensambladores: INCDIR, que sirve
para que el ensamblador encuentre los ficheros de cabecera en cualquier
directorio donde el usuario quiera tenerlos-. Para ensamblarlo con otros
paquetes basta con eliminar dicha directiva.
Y vamos con
el programa. Como verán comienza por incluir los ficheros de
cabecera necesarios para el tipo de aplicación que estemos construyendo
y, naturalmente, los que acabamos de escribir.
A continuación hay
unas definiciones externas (tablas para inicialización, funciones
estándar, etc) y, ¡oh maravilla!, los nombres reales de
nuestras funciones. Estas definiciones no son imprescindibles pero viene
bien hacerlas ya que pueden ser muy útiles a la hora de depurar
programas que hagan uso de la librería.
Luego vienen unas referencias
externas al código C, a la Base de Exec y a algunas de sus funciones
que utilizaremos más adelante. Después de estos preliminares
comienza el verdadero código ejecutable.
La primera
instrucción es una especie de "seguro" para evitar
que cualquier usuario emplee la librería como si fuese un programa
normal y corriente. Para ello se devuelve en D0 el valor "-1 que
hace las veces de código de error.
Ahora hay que inicializar
una "rom-tag" -el modelo de esta estructura, si algún
curioso quiere verlo, se encuentra en los ficheros "exec/resident.i"
en versión Ensamblador y en "exec/resident.h" en version
C-. Cada variable miembro se inicializa de la forma siguiente:
RT_MATCHWORD con RTC_MATCHWORD
que es un idenficador cuyo valor es una constante del sistema (Hex,
4AFC).
RT_MATCHTAG con un puntero
a la propia estructura.
RT_ENDSKIP con un puntero
al final de nuestro código para que Exec sepa donde acaba.
RT_FLAGS con
un indicador del sistema. En nuestro caso RTF_AUTOINIT que sirve para
decirle a Exec que la librería contiene su propio código
de inicio.
RT_VERSION con número
de versión.
RT_TYPE con el tipo de aplicación,
en este caso librería, para lo que usaremos otro indicador del
sistema: NT_LIBRARY.
RT_PRI con la prioridad de
carga, que para las librerías puede ser 0, valor que emplearemos
en la nuestra.
RT_NAME con un puntero al
nombre de la estructura Nodo de la Base y que será el que definimos
en "ilbmbase.i".
RT_IDSTRING con un puntero
a un "identificador para mantenimiento".
RT_INIT con un puntero a
unas tablas para inicialización.
Tras
la rom-tag viene la definición de algunas constantes, entre ellas
el número de versión y revisión y posteriormente
el "identificador para mantenimiento". Este no es más
que una cadena de caracteres que debe tener el siguiente formato: "nombre
versión.revisión (dia mes año)", <retorno
de carro>, <cambio de línea>, <NULL>. En nuestro
programa estos tres últimos caracteres se dan en decimal, pero
pueden utilizarse octal o hexadecimal.
Una vez creado el identificador
declaramos el nombre de la "dos.library" que utilizaremos
en el código de inicio y reservamos una palabra (16 bits) para
alineamiento en frontera de palabra, necesario para el 68000.
Como hemos
visto, en la estructura rom-tag hemos inicializado RT_FLAGS con el indicador
RTF_AUTOINIT. Esto obliga a que la variable miembro RT_INIT apunte a
unas tablas para inicialización. Pues bien, estas tablas son
las que van a continuación.
"Inic:" indica
el tamaño del área de datos de la librería.
"TablaFunciones:"
contiene punteros a las funciones terminando en -1.
Esto indica
que cada puntero es la dirección, dada en 32 bits, de cada función
(otra forma de construir esta tabla podría ser haciendo que su
primera palabra fuese -1, con lo que cada puntero se trataría
como el desplazamiento relativo del inicio de cada función desde
el principio de la tabla, pero como es algo más exótico
usamos el primer método).
"TablaDatos:" se
utiliza para inicializar la estructura Nodo, datos específicos
y areas de memoria de la librería.
"Inicio:" es nuestro
propio código de inicio, que se ejecutará justo después
de la inicialización del nodo y los datos específicos.
Este es el código estándar de las librerías del
sistema, pero como las funciones han sido desarrolladas en C le he añadido
dos instrucciones para salvar copias extras de punteros a SysBase y
DOSBase necesarias para Lattice. Y llegamos a las funciones. Pero no
a las nuestras, sino a un grupo de cuatro que todas las run-time (del
sistema como de usuario) deben poseer. Veamos por qué.
En el capítulo anterior,
al hablar de la utilización de las librerías, quedamos
en que para que esto fuese posible había que pedírselo
a Exec, llamando a su función OpenLibrary(), y que con esta llamada
Exec cargaba e inicializaba la librería requerida SIEMPRE QUE
ESTA DIESE SU PERMISO. Pues de dar ese permiso se encarga, precisamente,
la primera de estas funciones estándar: "Open". En
realidad cuando un programa llama a OpenLibrary() esta a su vez llama
a "Open" que es la verdadera encargada de abrir la librería.
Open incrementa la variable interna lib_OpenCnt para tomar nota de que
una aplicación va a usar la librería, activa un indicador
para evitar que otras aplicaciones pidan a Exec que la quite de su lista
(este indicador se llama de exclusión aplazada o "delayed
expunge") y devuelve un puntero a la Base como señal de
que puede utilizarse (es decir, da su "permiso de uso"). Pero
puede ocurrir que en cada nueva apertura por parte de otros programas,
o en la solicitada por el nuestro si ya está abierta, se reserve
memoria extra o que, simplemente, se hayan inscluido instrucciones en
la librería a fin de que sólo pueda ser usada por una
aplicación específica. En estos casos open devolverá
NULL, lo que puede interpretarse como la denegación del permiso.
La segunda
función, "Close", realiza su tarea cuando es llamada
por CloseLibrary() desde un programa. Close decrementa lib_OpenCnt y
comprueba si el indicador de exclusión aplazada está puesto.
Si lo está devuelve un puntero al área de datos. Si no
lo está llama a la función que veremos seguidamente para
que se encargue de quitarla, si se puede, de la lista de Exec y devuelve
NULL. En ambos casos la librería quedará "cerrada"
para el programa que la estiviese usando. La función que del
párrafo anterior es "Expunge". Esta se ejecuta cuando
es llamada internamente por Close o por "RemoveLibrary()"
desde un programa. Expunge quita la librería de la lista de Exec,
libera la memoria reservada durante la inicialización y la ocupada
por la propia Base.
Claro que esto se produce
cuando no hay ningún programa usando la librería y para
cerciorarse de ello Expunge comprueba lib_Open Cnt. Si puede realiza
la exclusión y para indicar que lo ha conseguido devuelve el
área de datos. Sino activa el indicador de exclusión aplazada
y devuelve NULL.
La última es "ExtFunc"
y se limita a devolver 0. Es una función reservada por Commodore
para futuras expansiones y actualmente no tiene utilidad, pero debe
estar ahí.
Y por fin llegamos
a las nuestras, las que con tanto cariño, esmero y dedicación
escribimos en el capítulo anterior. Precisamente por haberlas
creado fuera de este programa lo que va a continuación no son
"verdaderas" funciones (aunque de cara al usuario si que lo
parezcan y se usen como tale) sino unas rutinas de llamada. Además
al estar escrito su código en C y debido a que este lenguaje
requiere que los argumentos estén almacenados en la pila (no
los toma directamente de los registros como el Ensamblador) es necesario
que cada rutina incluya instrucciones al efecto. Vamos a ver como funcionan:
Primero pasan las argumentos
desde los registros asignados a cada una a la pila EN ORDEN INVERSO
a como aparecen en los prototipos de cada función, es decir de
último a primero, para que después el código C
pueda extraerlos de la pila en su orden natural de primero a último
(para que esto se vea más claro he preferio pasar los argumentos
uno a uno con "MOVE.L" en lugar de pasarlos en grupo con "MOVEM.L"
cuando fuese posible).
A continuación
llaman al código C. Este, una vez completado su trabajo, devuelve
el control a las rutinas para que estas restauren el apuntador de pila
y devuelvan el control al programa usuario de la librería, pasándole
el resultado por defecto en D0.
|
Para facilitar
el trabajo con las
librerías, puede usarse el fichero
"ilbm.lnk", que contiene todas las
instrucciones necesarias para Blink.
|
|
Y ya sólo queda inicializar
unas variables necesarias para el código C y terminar el programa
con la directiva "END".
Y ya está, ya podemos
ensamblarlo. Para ello seguid las instrucciones al efecto de vuestro
software porque, lógicamente, las que voy a dar yo son para Devpac
2. Allá van:
Desde el editor seleccione
en el menú "Progam" la opción "Assemble".
Cuando aparezca el requester seleccionen:
- Program type = Linkable
- Symbols case = Dependent
- List = lo que les dé la gana, pues no es importante
- Assembly = Fast
- Output = Disk (indicando vuestro path en el gadget de cadena, si no
van a guardar el código objeto en el directorio en el que estén
trabajando).
Si no habéis
cometido errores el programa se ensamblará normalmente y si han
metido la patita corrijan el fallo y repitan el proceso.
Ahora queda enlazarlo con
el código objeto de los módulos C y ya tenemos librerías
run-time cachi, como las que van con el sistema.
ENLACE E INSTALACION
Una vez más este paso
depende de las peculiaridades de vuestro "linker".
Los usuarios de SAS/Lattice
C y/o Devpac 2 estamos de suerte porque ambos paquetes inclyen el mismo,
"Blink", aunque en distinta versión. Yo aconsejo usar
la de SAS/Lattice porque es la más moderna y, lógicamente,
depurada. Lo mismo vale para la librería explorable "Amiga.lib"
con la que también hay que enlazar.
|
NOTA
DE REDACCION
Los
listados que se
publican corresponden a
las primeras partes
o rutinas de este capítulo
El
resto puede encontrarse
en nuestros discos
Amiga World 37,
o solicitarlos por correo,
enviando previamente
un disco formateado
en cualquier Amiga.
|
Para facilitar las cosas
pueden usar el fichero "ilbm.lnk" que contiene todas las instrucciones
necesarias para Blink. Basta con ejecutarlo para obtener, por fin, la
librería. Para ello, en Shell o CLI den el siguiente mandato:
blink with ilbm.lnk
Los usuarios de otros linkers
pueden servirse de ilbm.lnk para ver con qué módulos y
librerías hay que efectuar el enlace.
A continuación hay
que instalar la librería allí donde Exec pueda echarle
mano. Simplemente póngala en el directorio ":Libs/"
de su disco de trabajo y ya está.
Y Ahora viene la pregunta
del millón ¿se puede usar ya? Pues...¡no!. Antes
tendremos que desarrollar algunos ficheros especiales, pero eso lo dejaremos
para el siguiente capítulo.
**********************************************************
* FICHERO.I : ilbmbase.i *
* DESCRIPCION : Definicion de ILBMBase. *
* DESARROLLO : 17-06-92 - Creación (V. Santos) *
**********************************************************
IFND IFF_ILBMBASE_I
IFF_ILBMBASE_I SET 1
IFND EXEC_TYPES_I
INCLUDE "exec/types.i"
ENDC
IFND EXEC_TYPES_I
INCLUDE "exec/lists.i"
ENDC
IFND EXEC_LIBRARIES_I
INCLUDE "exec/libraries.i"
ENDC
;------------------------
; Estructura de ILBMBase
; -----------------------
STRUCTURE ILBMBase,LIB_SIZE
UBYTE ilbm_Flags
UBYTE ilbm_pad
ULONG ilbm_SysLib
ULONG ilbm_DosLib
ULONG ilbm_SegList
LABEL ILBMBase_SIZEOF
; nombre de la libreria
ILBMNOMBRE MACRO
DC.B 'ilbm.library',0
DS.W 0 ; necesario para Devpac
ENDM
ENDC
**** FIN DE ILBMbase.i
*********************************************************************
; with file para enlazado de modulos de ilbm.library
; USO:
; blink with ilbm.lnk
FROM ilbm.o+ilbmred.o+ilbmwrite.o
LIBRARY lib:amiga.lib
TO ilbm.library
;opcionales
VERBOSE
MAP ilbm.map
********************************************************************
* FICHERO.I : macroslib.i *
* DESCRIPCION : definicion de macros para desarrollo de librerias. *
* DESARROLLO : 17-06-92 - Creacion (V. Santos) *
********************************************************************
; poner a 0 cualquier registro de datos
CLEAR MACRO
moveq #0,\1
ENDM
; llamada a funciones de libreria via a6 sin especificar _LVO
CALLSYS MACRO
jsr _LVO\1(a6)
ENDM
; referencia a funciones externas sin especificar _LVO
XLIB MACRO
XREF _LVO\1
ENDM
**** FIN DE macroslib.i
*******************************************************************
; with file para enlazado de modulos de ilbm.library
; USO:
; blink with ilbm.lnk
FROM ilbm.o+ilbmread.o+ilbmwrite.o
LIBRARY lib:amiga.lib
TO ilbm.library
;OPCIONALES
VERBOSE
MAP ilbm.map
**************************************************************
* MODULO : ilbm.s *
* DESCRIPCION : libreria "run-time" - PROGRAMACION PRINCIPAL *
* DESARROLLO : 17-06-92 - Creacion (V.Santos) *
* Version 1.0 : Funciones de lectura/escritura de ficheros *
* IFF de subtipo ILBM. *
**************************************************************
SECTION texto,CODE
* FICHEROS DE CABECERA
==============================================================
INCDIR "sys:devpac/include.cbm/" ; mi path particular
NOLIST
INCLUDE "exec/type.i"
INCLUDE "exec/libraries.i"
INCLUDE "exec/lists.i"
INCLUDE "exec/alerts.i"
INCLUDE "exec/initializers.i"
INCLUDE "exec/resident.i"
INCLUDE "libraries/dos.i"
INCLUDE "iff/ilbmbase.i"
INCLUDE "macroslib.i"
LIST
* DEFINICIONES EXTERNAS
============================================================
XDEF Inic ; codigo de inicio especifico
XDEF Open ; funciones estandar del sistema
XDEF Close
XDEF Expunge
XDEF ExtFunc
XDEF NombreLib ; nombre de la libreria
XDEF CheckILBM ; funciones propias de la libreria
XDEF GetBMHeader
XDEF GetGfxMode
XDEF GetColMap
XDEF DisplayData
XDEF PutILBM
XDEF PutBMHeader
XDEF PutGfxMode
XDEF PutColMap
XDEF SaveData
XDEF PutILBMSize
* REFERENCIAS EXTERNAS
==========================================================
XDEF _checkilbm_c ; codigo C
XDEF _getbmheader_c
XDEF _getgfxmode_c
XDEF _getcolmap_c
XDEF _displaydata_c
XDEF _putilbm_c
XDEF _putbmheader_c
XDEF _putgfxmode_c
XDEF _putcolmap_c
XDEF _savedata_c
XDEF _putilbmsize_c
XDEF _AbsExecBase ; base de Exec
XLIB OpenLibrary ; funciones de Exec utilizadas
XLIB CloseLibrary ; ...en el programa
XLIB FreeMem
XLIB Remove
* PROGRAMA PRINCIPAL
=========================================================
Start:
moveq #-1,d0
;Estructura "rom-tag" necesaria para Exec y ramlib (en los comentarios
;se indica que variable miembro recibe cada dato).
PRIORIDAD EQU 0 ; no es necesario establecer prioridad
DescribeDatos:
; STRUCTURE RT,0
dc.w RTC_MATCHWORD ; UWORD RT_MATCHWORD
dc.l DescribeDatos ; APTR RT_MATCHTAG
dc.l Fin ; APTR RT_ENDSKIP
dc.b RTF_AUTOINIT ; UBYTE RT_FLAGS
dc.b VERSION ; UBYTE RT_VERSION
dc.b NT_LIBRARY ; UBYTE RT_RYPE
dc.b PRIORIDAD ; BYTE RT_PRI
dc.l NombreLib ; APTR RT_NAME
dc.l ldLib ; APTR RT_IDSTRING
dc.l Inic ; APTR RT_INIT
NombreLib: ILBMNOMBRE ; definido en "ilbmbase.i"
VERSION: EQU 1
REVISION: EQU 0
; Identificador utilizado para el mantenimiento de la libreria.
; Su formato siempre debe ser
; 'nombre version.revision (dd mmm aaaa)',,,
IdLib: dc.b 'ilbmlib 1.0 (17 Jul 1992)',13,10,0
dosNombre: DOSNAME ; nombre de la "dos.library"
dc.w 0 ; para alineamiento
; Tablas para inicializacion
inic:
dc.l ILBMBase_SIZEOF
dc.l TablaFunciones
dc.l TablaDatos
dc.l Inicio
TablaFunciones:
dc.l Open ; Funciones estandar
dc.l Close
dc.l Expunge
dc.l ExtFunc
dc.l CheckILBM ; funciones propias
dc.l GetBMHeader
dc.l GetGfxMode
dc.l GetColMap
dc.l DisplayData
dc.l PutILBM
dc.l PutBMHeader
dc.l PutGfxMode
dc.l PutColMap
dc.l SaveData
dc.l PutILBMSize
dc.l -1 ; indicador de fin de tabla
TablaDatos:
INITBYTE LN_TYPE,NT_LIBRARY
INITLONG LN_NAME,NombreLib
INITBYTE LIB_FLAGS,LIBF_SUMUSEDILIBF_CHANGED
INITWORD LIB_VERSION,VERSION
INITWORD LIB_REVISION,REVISION
INITLONG LIB_IDSTRING,IdLib
dc.l 0
;Codigo de inicio estandar
;(se han incluido dos instrucciones extra a fin de salvar copias de
;punteros a Exec y dos.library necesarias para SAS/Lattice C).
;Si devuelve un valor distinto de 0 la libreria puede ser añadida
;a la lista de librerias del sistema mantenida por Exec.
Inicio:
move.l a5,-(sp) ; preservar a5
move.l d0,a5 ; salvar puntero a la libreria
move.l a6,ilbm_SysLib ; salvar puntero a Exec
move.l a6,_SysBase ; copia para Lattice
move.l a0,ilbm_SegList ; salvar puntero a nuestro codigo
lea dosNombre(pc),a1 ; abrir dos.library
CLEAR d0
CALLSYS OpenLibrary
move.l d0,ilbm_DosLib(a5) ; salvar puntero a dos.library
move.l d0,_DOSBase ; copia para Lattice
bne 1$
ALERT AG_OpenLib!AO_DOSLib ;panico!!! dos.library no abierta
1$:
; Aqui se deben inicializar datos especificos de la aplicacion.
; En este caso ninguno.
move.l a5,d0 ; resultado en d0
move.l (sp)+,a5 ; restaurar a5
rts
* FUNCIONES ESTANDAR
=====================================================================
; Open
; Devuelve un puntero a la libreria si puede abrirla, si no
; devuelve NULL.
; la función puede fallar si se reserva memoria en cada apertura
; o si la libreria solo puede ser abierta por una sola aplicacion
; cada vez.
Open: ; (ptro a lib en a6, version en d0)
addq.w #1,LIB_OPENCNT(a6) ; tomar nota si ya esta abierta
bclr #LIBB_DELEXP,ilbm_Flags(a6) ; prevenir exclusiones
move.l a6,d0 ; resultado en d0
rts
; Close.
; Devuelve NULL si puede cerrar la libreria o el segmento de datos
; si hay alguna exclusion pendiente.
Close: ; (ptro a lib en a6)
CLEAR d0 ; fijar valor a devolver
subq.w #1,LIB_OPENCNT(a6) ; una aplicacion menos usandola
bne 1$
btst #LIBB_DELEXP.ilbm_Flags(a6) ; se puede excluir?
beq.s 1$ ; no, devolver resultado
bsr Expunge ; si, excluirla de lista del sistema
1$:
rts
; Expunge.
; Devuelve el segmento de datos si la libreria no va a estar
; abierta (en uso) por ninguna aplicacion. En caso contrario
; activa el indicador de "aplazar exclusion" y devuelve NULL.
Expunge: ; (ptro a lib en a6)
movem.l d2/a5/a6,-(sp) ; preservar registros
move.l a6,a5 ; pasar puntero a libreria a a5
move.l ilbm_SysLib(a5),a6 ; dejar puntero a Exec en a6
tst.w LIB_OPENCNT(a5) ; va a seguir abierta?
beq 1$ ; no, excluirla
bset #LIBB_DELEXP,ilbm_Flags(a5) ; si, activar indicador
CLEAR d0 ; ...y devolver resultado
bra.s finExpunge
1$:
move.l ilbm_SegList(a5),d2 ;segmento de datos en d2
move.l a5,a1 ; excluirla de lista del sistema
CALLSYS Remove
move.l ilbm_DosLib(a5),a1 ; cerrar dos.library
CALLSYS CloseLibrary
CLEAR d0 ; liberar memoria
move.l a5,a1
move.w LIB_NEGSIZE(a5),d0
sub.l d0,a1
add.w LIB_POSSIZE(a5),d0
|
|