Obligement - L'Amiga au maximum

Jeudi 25 avril 2024 - 00:24  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

Actualité (récente)
Actualité (archive)
Comparatifs
Dossiers
Entrevues
Matériel (tests)
Matériel (bidouilles)
Points de vue
En pratique
Programmation
Reportages
Quizz
Tests de jeux
Tests de logiciels
Tests de compilations
Trucs et astuces
Articles divers

Articles in english


Réseaux sociaux

Suivez-nous sur X




Liste des jeux Amiga

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z,
ALL


Trucs et astuces

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Glossaire

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Galeries

Menu des galeries

BD d'Amiga Spécial
Caricatures Dudai
Caricatures Jet d'ail
Diagrammes de Jay Miner
Images insolites
Fin de jeux (de A à E)
Fin de Jeux (de F à O)
Fin de jeux (de P à Z)
Galerie de Mike Dafunk
Logos d'Obligement
Pubs pour matériels
Systèmes d'exploitation
Trombinoscope Alchimie 7
Vidéos


Téléchargement

Documents
Jeux
Logiciels
Magazines
Divers


Liens

Associations
Jeux
Logiciels
Matériel
Magazines et médias
Pages personnelles
Réparateurs
Revendeurs
Scène démo
Sites de téléchargement
Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec


A Propos

A propos d'Obligement

A Propos


Contact

David Brunet

Courriel

 


En pratique : LibTool, le créateur de bibliothèque
(Article écrit par Pascal Amiable et extrait d'Amiga News Tech - mai 1992)


Cet article a été largement inspiré par un travail que j'avais à effectuer il y a de cela quelques mois, à savoir la réalisation d'une bibliothèque de fonctions mathématiques partageable par plusieurs tâches. C'est-à-dire, dans la terminologie propre à Amiga, une "shared library" (bibliothèque partagée). J'avoue qu'à cette époque si j'avais possédé LibTool, j'aurais largement augmenté mon temps de sommeil...

Lorsque, sur un ordinateur, vous développez de grosses applications, vous êtes obligés, à un moment ou à un autre, de découper votre programme en plusieurs morceaux. Ce découpage peut être réalisé de deux manières différentes : soit grâce à des overlays, soit à l'aide de bibliothèques. Une structure à base d'overlays est, je vous le rappelle, une décomposition sous forme arborescente de votre programme, en morceaux (généralement des fonctions distinctes du programme) qui sont chargés et déchargés lors de leurs appels, le chargement et le déchargement étant effectué automatiquement. Très simples à mettre en oeuvre, les overlays ont toutefois deux inconvénients : d'abord la complexification rapide de l'arborescence pour de très grosses applications, et les risques de duplication du code si les overlays sont mal conçus (pour de plus amples informations sur le sujet reportez-vous à l'article "Les Overlays" paru dans le numéro 27 de l'ANT).

Pour remédier à ces problèmes, on utilisera la notion de "library" ou, en français bien de chez nous, de bibliothèque de fonctions. Cette notion consiste à regrouper sous la forme de composants logiciels, généralement thématiques, des fonctions indépendantes de bas niveau (fonctions mathématiques ou de gestion des entrées/sorties par exemple). Lorsque votre programme appelle cette fonction, la bibliothèque associée est chargée en mémoire. La fonction est exécutée et les résultats sont renvoyés au programme principal. Totalement satisfaisante sur des machines monotâches, cette méthode montre vite ses limites sur des machines multitâches : comme ces bibliothèques sont indépendantes de tout autre composant logiciel, lorsque deux programmes utilisent la même bibliothèque, cette dernière est recopiée deux fois en mémoire. Cette surconsommation de mémoire étant, vous l'avouerez, très problématique, il était nécessaire d'y remédier en introduisant le concept de bibliothèques partagées.

Une bibliothèque partagée n'est plus, cette fois, totalement indépendante, mais liée au système d'exploitation. Ce dernier est donc en mesure de savoir, lorsqu'une tâche appelle une bibliothèque, si cette dernière est déjà présente en mémoire et dans l'affirmative, ne la recharge pas. Le code associé aux bibliothèques partagées étant réentrant plusieurs applications peuvent alors y accéder en même temps.

Toute série de routines qui est susceptible d'être utilisée par plusieurs applications doit donc être incluse dans une bibliothèque. Des exemples classiques de ce regroupement peuvent être facilement trouvées au niveau du système de l'Amiga comme les bibliothèques Graphics et Intuition.

Enfin, dernier avantage des bibliothèques de fonctions, vous pouvez offrir à d'autres développeurs des fonctions sous forme de bibliothèques qu'ils peuvent utiliser dans leurs propres programmes sans que vous soyez obligé de donner les sources associés ou que vous les obligiez à utiliser un langage particulier.

Bienvenue dans un monde meilleur...

A l'époque ou j'étais intéressé par la construction d'une bibliothèque de fonctions, j'ai étudié un grand nombre d'exemples dont le point commun était une complexité certaine, tant au niveau de la conception de la bibliothèque qu'au niveau de sa maintenance et de son évolution. Ayant découvert LibTool et ayant été surpris par sa simplicité d'utilisation, j'ai donc décidé de vous expliquer son fonctionnement, sur un exemple simple, afin que vous puissiez vous aussi goûter aux joies des bibliothèques sans les inconvénients liés à leur construction.

Notre exemple va consister à convertir une série de fonctions C travaillant sur les vecteurs (addition, soustraction, produit scalaire et produit vectoriel) en une bibliothèque partagée. On utilisera, outre LibTool, le compilateur Lattice C (vous pouvez faire de même avec votre compilateur favori). La première chose que nous allons étudier c'est la structure d'une bibliothèque partagée.

Structure d'une bibliothèque partagée

Avant d'étudier cet exemple, il me semble nécessaire d'effectuer quelques rappels sur la création des bibliothèques sur Amiga. Une bibliothèque de fonctions partagées se compose de quatre parties distinctes :
  • Une structure de contrôle de la bibliothèque.
  • Une table de saut vers les fonctions (appelée également Vector Table).
  • Le code exécutable des fonctions proprement dit.
  • La zone des données associées à l'ensemble des fonctions de la bibliothèque.
Une fois en mémoire, la bibliothèque peut être schématisée de la manière suivante :

LibTool
Figure 1

La structure de contrôle est utilisée, entre autres, par le moniteur de contrôle d'Exec pour gérer son chargement et son déchargement.

Cette structure est définie comme suit :

LibTool

Le champ "lib_Flags" est utilisé en interne par Exec pour suivre l'état de la bibliothèque. Les champs "lib_PosSize" et "lib_NegSize" contiennent la taille en octets des deux parties de la bibliothèque située de part et d'autre de l'adresse de base (Cf. fig. 1).

Les champs "lib_Version" et "lib_Revision" sont utilisables pour marquer la version exacte de la bibliothèque à un moment donné (pour effectuer des contrôles de versions lors de l'appel à cet bibliothèque).

Le champ "lib_IdString" est un pointeur sur une chaîne de caractères qui peut contenir des informations d'ordre général sur le produit (un texte de votre choix en quelque sorte).

"lib_Sum" est le champ qui contient la somme de contrôle (checksum) associée à la bibliothèque. Cette somme de contrôle est utilisée par Exec pour s'assurer de l'intégrité de la bibliothèque une fois son chargement effectué. Ceci permet de contrôler d'éventuels écrasements mémoire dus à d'autres tâches.

Enfin, "lib_OpenCnt" est un compteur qui est incrémenté à chaque ouverture de la bibliothèque par OpenLibrary() et décrémenté à chaque fermeture par CloseLibrary(). Lorsque ce compteur vaut zéro cela signifie que plus personne n'utilise la bibliothèque et, par conséquent, Exec peut au besoin la décharger pour récupérer la place mémoire.

Une fois les paramètres associés à cette structure entièrement renseignés, nous pouvons créer la Vector Table définissant les différents décalages ("offsets") associées aux fonctions. Chaque élément de cette table est codé sur six octets. Les deux premiers représentent le codage d'une instruction assembleur de saut (JMP) et les quatre suivants, l'adresse de saut à cette fonction. Cette table de vecteur est référencée en négatif par rapport à l'adresse de base. Par exemple le vecteur associé à la fonction numéro 6 se trouve à l'adresse -36. Ces multiples de six sont également appelés "Library Vector Offsets" ou LVO dans les publications anglo-saxonnes.

Fonctions privées

En plus des fonctions applicatives proprement dites, il est nécessaire, pour chaque bibliothèque, d'écrire quatre fonctions de gestion nommées Open(), Close(), Expunge() et Reserved().
  • Open() est appelée par OpenLibrary() à chaque fois qu'un programme tente d'ouvrir votre propre bibliothèque.
  • Close() est utilisée de la même manière par CloseLibrary(), pour la refermer.
  • Expunge() est utilisée lors du déchargement de la bibliothèque par Exec pour effectuer le nettoyage de la mémoire. Elle est donc appelée lors du passage à zéro du champ lib_OpenCnt.
  • Reserved() est une fonction réservée à des usages futurs. A l'heure actuelle, est doit impérativement retourner 0.
Afin de charger correctement une bibliothèque de fonctions, il est nécessaire de créer une structure RomTag pour Exec. Cette structure est la suivante :

LibTool
LibTool

Remarquez au passage que le nombre $4AFC est le code machine de l'instruction assembleur ILLEGAL ; il est utilisé par Exec pour rechercher d'autres RomTags en mémoire lors de l'initialisation du système (revoyez donc l'article sur l'utilitaire NewPic). Cette structure doit être assemblée sous forme d'un fichier objet pour être reliée avec le reste, comme nous allons le voir maintenant.

Pour créer une bibliothèque, il vous faudra donc, outre vos propres routines, compiler et relier les quatre fonctions de gestion de la bibliothèque, la table des vecteurs de saut et la structure RomTag. Il s'agit là d'un travail en général pénible et répétitif, et qui peut être sans trop de problèmes automatisé - c'est justement ce que LibTool se propose de faire.

Règles de programmation

Avant d'utiliser LibTool, il est nécessaire de définir quelques règles de programmation pour la création d'une bibliothèque. Primo, le code source que vous développez doit être réentrant, ce qui signifie que vous ne devez en aucun cas faire usage de variables globales, tout du moins pas sans certaines précautions. En effet, comme il est possible que plusieurs tâches utilisent en même temps la bibliothèque, voire la même fonction, elles peuvent accéder en même temps en écriture aux variables globales qui sont, quant à elles, uniques. Je vous laisse le soin d'imaginer l'imprévisibilité du fonctionnement de tels programmes.

Secundo, si votre bibliothèque a besoin, pour une raison ou pour une autre d'utiliser des fonctions appartenant à une autre bibliothèque, vous devez obligatoirement ouvrir cette dernière au sein de la fonction Open() et la refermer lors du déchargement de la bibliothèque dans la fonction Close(). Un exemple typique d'une telle utilisation, est une bibliothèque utilisant les fonctions internes de l'Amiga (Graphics, Intuition...).

Tertio, vous ne devez pas appeler de fonctions du style printf() ou scanf() utilisant l'une des trois entrées/sorties standard stdin, stdout et stderr. La raison en est simple, ces entrées/sorties standard ne sont pas accessibles aux bibliothèques partagées. Pour gérer vos erreurs, une solution de remplacement consiste à renvoyer de simples codes d'erreur qui seront traités au niveau du programme appelant.

Description de notre exemple

La bibliothèque d'exemple que nous allons créer contient quatre fonctions de traitement vectoriel dans l'espace. Cet exemple a le gros avantage d'être très court tout en reflétant correctement l'usage des bibliothèques. Ces quatre fonctions sont :
  • VECTEUR *addition (v1,v2) : cette fonction réalise l'addition de deux vecteurs (définis par un tableau contenant trois flottants) et retourne un pointeur sur le vecteur résultat.
  • VECTEUR *soustraction(v1,v2) : comme addition() à la différence près qu'elle réalise la soustraction de deux vecteurs.
  • float pscalaire(v1,v2) : réalise le produit scalaire des deux vecteurs v1 et v2 et retourne le flottant résultat.
  • VECTEUR *pvectoriel(v1,v2) : calcule le produit vectoriel de v1 par v2 et retourne un pointeur sur ce vecteur résultat.
Avant d'étudier comment transformer nos quatre fonctions en bibliothèque, voici le descriptif et le code associé à ces fonctions :
  • float *addition(v1,v2) calcule le vecteur résultant de l'addition de deux vecteurs (représentés par des tableaux de trois nombres flottants).
  • float *soustraction(v1,v2) calcule le vecteur résultant de la soustraction de v1 et v2.
  • float pscalaire(v1,v2) calcule le produit scalaire des deux vecteurs v1 et v2.
  • float *pvectoriel(v1,v2) calcule le produit vectoriel de v1 par v2.
Voici le code de ces différentes fonctions.

LibTool
LibTool

Comme vous pouvez le remarquer, il s'agit d'un code réentrant puisqu'aucune variable globale n'est déclarée. Notre bibliothèque faisant partie à l'origine d'un paquet plus complet nécessitant l'utilisation des bibliothèques système mathffp.library et mathtrans.library, nous allons réaliser deux fonctions, Open() et Expunge(), qui se chargent de les ouvrir et de les fermer en même temps que la veclib.

LibTool

La majeure partie du travail de création des bibliothèques va être prise en charge par LibTool, qui va automatiquement créer le module de démarrage de la bibliothèque (startup module), c'est-à-dire le RomTag, la structure Library, la table des fonctions spécifiques, les quatre fonctions standard Open(), Close(), Expunge() et Reserved() et le code pour l'ouverture des bibliothèques standard Exec, Dos, Intuition et Graphics.

Afin de vous aider à écrire le code de l'application qui utilisera la bibliothèque, LibTool génère aussi un fichier en-tête constitué soit de routines "glue" (de liaison), soit de pragmas. Les routines de liaison sont utilisées pour récupérer les arguments passés via la pile et les stocker dans les registres appropriés avant d'appeler la bibliothèque. Bien sûr, l'utilisation de pragmas élimine automatiquement les routines de liaison.

Les données nécessaires à la réalisation de ces différents éléments seront transmises à LibTool via un fichier ".FD". Bien connu des programmeurs sur Amiga, ce fichier contiendra des commandes supplémentaires reconnues uniquement par LibTool. Voici celui correspondant à notre bibliothèque :

LibTool

Vous noterez que tout ce qui est nécessaire à la description de la bibliothèque se trouve dans ce fichier. Une fois ce fichier écrit nous allons le sauvegarder sous le nom de veclib.fd et utiliser LibTool.

La syntaxe de cette commande est la suivante :

LibTool [-ibpschalm] [-e extension] [-o out] fdname1 ...
  • -i inclut également les fonctions privées (##private).
  • -b génère un fichier BMAP (à la place des routines de liaison, des pragmas ou des fichiers include).
  • -p génère une série de directives #pragma (à la place du code de liaison ou du fichier BMAP).
  • -s détruit le registre A6 dans le code de liaison (sauvegardé par défaut).
  • -c crée les fichiers de liaison et pragmas ainsi que le code de démarrage pour l'utiliser avec des fonctions écrites en C.
  • -1 génère des pragmas pour le Lattice (Manx par défaut) combine les pragmas, BMAP ou de liaison dans un seul fichier (par défaut, LibTool génère un fichier par fonction).
  • -m génère un code de démarrage pour la bibliothèque (module assembleur .src).
  • -a génère un include assembleur à la place des pragmas, du code de liaison ou BMAP.
  • -h génère le fichier include C.
  • -e utilise une extension autre que ".asm" pour le fichier de liaison.
Nous allons créer à la fois le fichier de liaison, les includes C et le source de démarrage.

libtool -cmh -o glue.asm veclib.fd

L'option "-c" indique à LibTool que notre bibliothèque de fonctions est écrite en C et par conséquent, que l'application va passer les arguments via la pile. L'option "-h" indique qu'il est nécessaire de créer les includes associés aux fonctions de la bibliothèque pour les inclure dans l'application. Enfin, l'option "-m" demande de créer le startup-code associé à la bibliothèque.

L'option "-o glue.asm" demande la création du fichier de liaison contenant les stubs pour chaque fonction. Si nous avions voulu obtenir un fichier pragmas nous aurions rajouté l'option "-p".

Une fois la commande exécutée, vous aurez dans le répertoire courant trois nouveaux fichiers (glue.asm, veclib.h et veclib.src) représentant respectivement le fichier de liaison, les includes et le startup-code de la bibliothèque.

Il ne reste plus désormais qu'à :
  • Compiler le fichier source des fonctions de la bibliothèque.
  • Assembler le fichier veclib.src en libstart.o.
  • Relier l'ensemble des fichiers en commençant toujours par le fichier de démarrage libstart.o.
La bibliothèque est alors créée.

Voici l'ensemble des commandes résumées pour obtenir notre bibliothèque avec les deux compilateurs les plus célèbres du marché.

Pour le Manx :

cc +pb veclib.c
LibTool -cmho glue.asm veclib.fd
as -cd -o LibStart.o veclib.src
ln -o libs:veclib.library LibStart.o veclib.o -lcl32

Pour le Lattice

lc -b0 veclib.c
LibTool -cmo glue.asm veclib.fd
asm -olibstart.o libstart.src
blink LibStart.o veclib.o LIB lib:lcnb.lib TO libs:veclib.library

Pour utiliser notre bibliothèque, il suffit dès lors d'inclure dans le fichier source les includes générés :

#include "veclib.h"

...et d'ouvrir notre bibliothèque au début du programme, comme n'importe quelle autre bibliothèque système :

LibTool

Enfin, on compilera et reliera l'application avec le module de liaison (ou les pragmas) comme suit :

Pour le Manx

cc +p test.c
as -cd glue.asm
ln -o ram:Test test.o glue.o -lcl32

Pour le Lattice

lc -b0 test.c
asm glue.asm
blink lib:c.o test.o glue.o LIB lib:lcnb.lib TO ram:Test

Voilà, notre application est prête à fonctionner. Il existe bien entendu de nombreuses précisions à apporter sur les bibliothèques partagées.


[Retour en haut] / [Retour aux articles]