Come creare una shared library con il SAS/C




Con il SAS/C 6.0 o superiori è possibile creare delle shared library (librerie condivise). In effetti questa possibilità esiste dalla versione 5.0, ma ora il procedimento è più affidabile e meglio documentato.
Esaminiamo dunque i passi necessari a realizzare una propria libreria, prima per sommi capi e poi attraverso un esempio pratico.




Per creare una shared library occorre innanzitutto scrivere il codice delle funzioni che essa dovrà contenere. Questo può essere organizzato in uno o più file, esattamente come nel caso delle linker library, ma deve in più specificare, per ogni funzione, i registri nei quali la funzione stessa riceve i suoi argomenti. Questo si ottiene mediante l'uso delle parole chiave __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:

Per il compilatore
ParametroCommento
LIBCODE Obbligatorio
SAVEDS Alternativamente si può usare la parola chiave __saveds

Per il linker
ParametroCommento
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).




Per chi è già esperto di programmazione in C su Amiga, probabilmente quanto detto è già sufficiente per capire come creare una libreria. I neofiti, d'altra parte, troveranno utile che si faccia un esempio per illustrare meglio i concetti esposti.




Supponiamo di volere una libreria con tre funzioni, chiamate Uno(), Due() e Tre(), tutte ad un solo argomento. L'idea è la seguente: A questo scopo potremmo scrivere il seguente codice (assumiamo, per il momento, che la graphics.library e la intuition.library siano già aperte):

#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);
   }
}

[...]





Un'ultima nota: specificando il modulo 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.
Per creare una libreria che ad ogni apertura generi una nuova copia dei propri dati globali, diversa per ciascun task chiamante, occorre specificare invece il modulo libinitr.o (notare la 'r').




Questo è solo un sunto delle operazioni da svolgere per creare una nuova libreria; informazioni più dettagliate si trovano, naturalmente, nella documentazione originale del SAS/C 6.x, sia sul manuale che in due esempi di libreria collocati su dischetto nelle directory extras/examples/samplelib ed extras/examples/reslib.


Pagina Principale


    Scritto da: Massimo Tantignone  e-mail: tanti@intercom.it
                Via Campagnoli, 4
                28100 Novara
                ITALY