__asm
e register __xx
del SAS/C.
Il passo successivo consiste nello scrivere il file .fd
della libreria,
un semplice file ASCII che serve a descrivere l'ordine e i parametri delle
funzioni in essa contenute.
Per trasformare il codice sorgente in un file .library
funzionante è poi
sufficiente eseguirne la compilazione ed il linking avendo la cura di
specificare alcuni (semplici) parametri aggiuntivi.
Tali parametri sono i seguenti:
Parametro | Commento |
---|---|
LIBCODE
| Obbligatorio |
SAVEDS
| Alternativamente si può usare la parola chiave __saveds
|
Parametro | Commento |
---|---|
FROM LIB:libent.o LIB:libinit.o
| Obbligatorio, moduli di startup |
LIBPREFIX <_prefisso>
| Obbligatorio, prefisso delle funzioni |
LIBFD <nomefile.fd>
| Obbligatorio, file .fd per la libreria
|
LIBID <id>
| Opzionale, stringa di ID della libreria |
LIBVERSION <versione>
| Opzionale, versione della libreria |
LIBREVISION <revisione>
| Opzionale, revisione della libreria |
È infine necessario utilizzare il programma FD2Pragma
fornito con il SAS/C
allo scopo di ricavare, a partire dal file .fd
, il file di inclusione
contenente le direttive #pragma
per il preprocessore (indispensabili per
l'utilizzo effettivo della libreria da parte degli applicativi scritti in C).
Uno()
, Due()
e Tre()
, tutte ad un solo argomento.
L'idea è la seguente:
Uno()
restituisce semplicemente il valore del suo argomento.
Due()
tenta di aprire il font "topaz 8" con la funzione
OpenFont()
della graphics.library, e restituisce il doppio del valore
del suo argomento in caso di successo e zero in caso contrario.
Tre()
, infine, fa lampeggiare lo schermo chiamando la
funzione DisplayBeep()
della intuition.library e restituisce il triplo
del valore del suo argomento.
#include "graphics/text.h" #include "proto/graphics.h" #include "proto/intuition.h" int __asm PR_Uno(register __d1 int a) { return (a); } int __asm PR_Due(register __d1 int a) { struct TextFont *tf; struct TextAttr ta = { "topaz.font", 8, 0, 0 }; if (tf = OpenFont(&ta)) { CloseFont(tf); return (a * 2); } else { return (0); } } int __asm PR_Tre(register __d1 int a) { DisplayBeep(NULL); return (a * 3); } |
Il prefisso PR_
davanti al nome delle funzioni è utile per motivi che
vedremo tra poco; in ogni caso è totalmente arbitrario, cioè avremmo potuto
usare qualunque altro prefisso.
Le parole chiave __asm
e register
servono per comunicare al compilatore
che le funzioni ricevono i propri argomenti nei registri e non sullo stack,
mentre __d1
indica che nel nostro caso deve essere utilizzato il registro
D1 della CPU. Ogni parametro delle funzioni della futura libreria deve essere
preceduto dalla coppia register __dn
(oppure register __an
) dove n
sta per
una cifra da 0 a 7.
A questo proposito, è consigliabile utilizzare i registri Dn
per gli
argomenti numerici e i registri An
per quelli di tipo puntatore.
Una volta steso il codice della libreria occorre scrivere il file .fd
contenente la descrizione di ogni funzione e dei suoi parametri.
Nel nostro caso potremmo scrivere un file .fd
come il seguente:
##base _ProvaBase ##bias 30 Uno(n)(D1) Due(n)(D1) Tre(n)(D1) ##end |
dove abbiamo indicato ProvaBase
come nome del puntatore alla base della
libreria. Per i nostri scopi non occorre conoscere il significato della
direttiva ##bias
; basti sapere che il valore da indicare dopo di essa è
praticamente sempre 30.
Supponiamo di salvare questo file .fd
con il nome prova_lib.fd
.
Per creare la libreria non resta che compilare ed effettuare il linking.
La compilazione può essere eseguita nel modo consueto, ma è necessario
utilizzare le parole chiave LIBCODE
e SAVEDS
nella linea di comando.
Ad esempio, se abbiamo chiamato il nostro sorgente Prova.c
, daremo:
SC LIBCODE SAVEDS [altre eventuali opzioni] Prova.c
e otterremo, salvo errori nel sorgente, il modulo oggetto Prova.o
.
Il parametro SAVEDS
può essere tralasciato se (e solo se) si ha la cura
di aggiungere l'analoga parola chiave __saveds
davanti al nome delle funzioni
nel proprio codice, ad esempio:
int __saveds __asm PR_Uno(register __d1 int a)
Questa alternativa è utile nel caso in cui il codice contenga molte
funzioni di servizio utilizzate solo localmente; in questo caso usare SAVEDS
globalmente nella chiamata al compilatore creerebbe un certo overhead, poiché
le funzioni locali non ne hanno bisogno.
Il linking va effettuato con la seguente istruzione:
SLink FROM LIB:libent.o LIB:libinit.o Prova.o TO LIBS:prova.library
LIB LIB:sc.lib LIBPREFIX _PR_ LIBFD prova_lib.fd
dove prova.library
è l'ipotetico nome della nuova libreria.
Come si vede, dopo la parola chiave LIBPREFIX
occorre indicare il prefisso
da noi posto davanti al nome di tutte le funzioni della libreria (se tale
prefisso esiste). Nel nostro caso il prefisso è PR_
, a cui qui si deve
premettere un ulteriore underscore ('_
') poichè il compilatore ne aggiunge
sempre uno ad ogni funzione nel modulo oggetto prodotto (ricordiamo che ora
stiamo dando istruzioni al linker, che lavora su moduli oggetto e non su
sorgenti).
Si noti (vedi sopra) che il prefisso usato nel sorgente NON deve comparire
nel file .fd
; inoltre, se non abbiamo usato alcun prefisso, occorre comunque
indicare un '_
' dopo LIBPREFIX
(senza virgolette).
L'utilità del prefisso consiste nel fatto che esso permette di scrivere
moduli ASM contenenti funzioni di interfaccia tra il linguaggio C e le
funzioni della libreria, cioè funzioni che copino sullo stack i valori degli
opportuni registri. Senza il prefisso non sarebbe possibile dare alle
funzioni di interfaccia lo stesso nome di quelle reali!
In effetti ormai questa caratteristica è obsoleta, il SAS/C è ora in grado
di gestire direttamente le chiamate alle funzioni di libreria mediante la
direttiva #pragma
del preprocessore, comunque il prefisso può ancora essere
utile, se non altro per distinguere a colpo d'occhio, nel proprio codice, tra
le funzioni "pubbliche" della libreria e quelle interne ad uso locale.
Ma torniamo alla nostra istruzione di linking.
Dopo la parola chiave LIBFD
dev'essere specificato il nome del file .fd
creato per la nuova libreria, nel nostro caso prova_lib.fd
.
All'istruzione possono poi naturalmente essere aggiunte altre opzioni come
le solite NOICONS
, SMALLCODE
, ecc.
Se tutto è stato svolto senza errori, a questo punto dovrebbe essere
stato creato il file prova.library
nella directory LIBS:
di sistema.
In realtà questo esempio non è completo in quanto non abbiamo provveduto ad aprire le due librerie di sistema (graphics e intuition) che la nostra prova.library utilizza.
Potremmo risolvere il problema inserendo direttamente nelle funzioni
Due()
e Tre()
il codice di apertura e chiusura delle librerie, ma questo
creerebbe un certo overhead. È molto più efficiente fare in modo che
le librerie necessarie siano aperte una volta sola all'inizio e chiuse
una volta sola alla fine.
A questo scopo si può approfittare di una possibilità offerta dal SAS/C:
è consentito infatti ridefinire due funzioni interne, __UserLibInit()
e
__UserLibCleanup()
, che vengono automaticamente eseguite all'atto
dell'apertura e della chiusura della nostra libreria.
Per l'esattezza la __UserLibInit()
è chiamata quando la nostra libreria
viene aperta con OpenLibrary()
, mentre la __UserLibCleanup()
è invocata in
seguito alla chiusura con CloseLibrary()
.
Conviene quindi eseguire l'apertura delle librerie che ci servono (e in
generale l'allocazione di qualunque risorsa globale) nella __UserLibInit()
,
e la corrispondente chiusura (o deallocazione) nella __UserLibCleanup()
.
La convenzione usata è che la __UserLibInit()
restituisca il valore 0 se
le sue operazioni si sono svolte senza errori, consentendo così di portare
a termine l'apertura della nostra libreria, e qualunque altro valore per
indicare un errore. In quest'ultimo caso la libreria non si aprirà.
La __UserLibCleanup()
, invece, non restituisce alcun valore, pertanto va
dichiarata di tipo void
.
Entrambe le funzioni ricevono come unico argomento il puntatore alla base della libreria che sta venendo aperta (o chiusa) nel registro A6.
Per completare l'esempio, quindi, possiamo scrivere un modulo aggiuntivo,
chiamato, mettiamo, UserLib.c
, simile al seguente:
#include "exec/libraries.h" #include "proto/exec.h" struct Library *GfxBase; struct Library *IntuitionBase; int __asm __UserLibInit(register __a6 struct Library *libbase) { GfxBase = OpenLibrary("graphics.library",0L); IntuitionBase = OpenLibrary("intuition.library",0L); if (GfxBase && IntuitionBase) { return (0); } else { if (GfxBase) CloseLibrary(GfxBase); if (IntuitionBase) CloseLibrary(IntuitionBase); return (1); } } void __asm __UserLibCleanup(register __a6 struct Library *libbase) { if (GfxBase) CloseLibrary(GfxBase); if (IntuitionBase) CloseLibrary(IntuitionBase); } |
Ora compiliamo questo modulo esattamente come fatto per il precedente
Prova.c
, e rieseguiamo il linking nel modo seguente:
SLink FROM LIB:libent.o LIB:libinit.o UserLib.o Prova.o
TO LIBS:prova.library LIB LIB:sc.lib LIBPREFIX _PR_
LIBFD prova_lib.fd
A questo punto la nostra nuova libreria sarà effettivamente funzionante.
Nel caso in cui la libreria da creare non richieda l'allocazione di
particolari risorse globali si può evitare di ridefinire la __UserLibInit()
e la __UserLibCleanup()
, in quanto una loro versione standard è già presente
nel modulo oggetto libinit.o
che viene sempre collegato alla libreria.
In questo caso non occorre scrivere né compilare moduli aggiuntivi come
il nostro UserLib.c
(esattamente come nella versione iniziale dell'esempio).
Come già accennato, è possibile usare anche le keyword LIBVERSION
e
LIBREVISION
con SLink
per assegnare un numero di versione e di revisione
alla libreria, e la keyword LIBID
per assegnare una stringa di versione.
Per esempio:
SLink [...] LIBVERSION 43 LIBREVISION 114 LIBID "prova 43.114 (25.9.96)"
L'ultimo passo da compiere consiste nel ricavare il file con le direttive
#pragma
per la libreria con l'utility FD2Pragma
:
FD2Pragma prova_lib.fd prova_pragmas.h
Fatto ciò è finalmente possibile usare la libreria nel solito modo nei propri programmi, ad esempio:
[...] #include "exec/libraries.h" #include "proto/exec.h" #include "prova_pragmas.h" struct Library *ProvaBase; main(void) { [...] ProvaBase = OpenLibrary("prova.library",0L); if (ProvaBase) { printf("%d * 3 = %d\n",4,Tre(4)); [...] CloseLibrary(ProvaBase); } } [...] |
libinit.o
nella chiamata al linker
(come nel nostro esempio) si ottiene una libreria con un'unica copia dei
propri dati globali, condivisa tra tutti i programmi che hanno aperto la
libreria stessa. libinitr.o
(notare la 'r
').
extras/examples/samplelib
ed
extras/examples/reslib
.
Scritto da: Massimo Tantignone e-mail: tanti@intercom.it Via Campagnoli, 4 28100 Novara ITALY