|
Programación de librerías
ILBM 2ª parte
Por Vicente Santos
CAPÍTULO 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 imprescindibles.
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 versión C-. Cada variable miembro se inicializa de la forma siguiente:
RT_MATCHWORD con RTC_MATCHWORD que es un identificador 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 (día 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 incluido 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 preferido 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 chachi, como las que van con el sistema.
ENLACE E INSTALACIÓN
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 incluyen 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
|
|