Revistas en papel
 Amiga World Nº37 - Noviembre 1992
Anterior
MenĂº
Logotipo

El Amiga Me Encanta ha conseguido el permiso por escrito de IDG Comunications España
para ofrecer los artículos de la revista Amiga World España.

     LIBRERÍAS     
Amiga World

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

Envía esta página web a un amigo:
Esta opción está desactivada temporalmente, rogamos disculpen las molestias

Volver a la página anterior

Al menú principal