Le C sur Amiga
Un article de GuruMed.
Sommaire |
Includes et stub libs: comment s'en servir.
par StAn.
La principale différence entre le "bête" C ANSI/ISO et le C sur Amiga est l'utilisation de bibliothèques partagées. La quasi intégralité de l'AmigaOS se trouve dans des bibliothèques partagées (exec.library, dos.library, intuition.library...), et il existe également de nombreuses bibliothèques additionnelles créées par des auteurs indépendants.
Le langage C utilise la pile pour passer les paramètres aux fonctions, tandis que ce sont les registres du 68k qui sont utilisés pour les fonctions de l'AmigaOS et des autres bibliothèques. Pour utiliser ces dernières, un programmeur doit donc faire appel à des fichiers d'includes ou à des link-libs particuliers. Ces fichiers ne sont pas les mêmes selon le compilateur que vous utilisez, et les auteurs de bibliothèques ne fournissent pas toujours ceux dont vous avez besoin.
Cet article vise à clarifier le sujet des inlines, pragmas, protos, fd, sfd, clib et autres stub-libs utilisés sur Amiga, et à vous expliquer comment les générer s'il vous en manque pour utiliser une bibliothèque particulière. Il abordera également brièvement le sujet de l'amiga.lib.
Le répertoire clib
Ce répertoire contient les prototypes des fonctions des bibliothèques partagées. C'est du C standard: le compilateur doit connaitre le prototype d'une fonction avant que celle-ci ne soit appelée dans un source. Le fichier clib d'une bibliothèque doit donc être inclus dans tout programme souhaitant utiliser cette dernière.
Extrait du fichier clib/amigaguide_protos.h:
LONG LockAmigaGuideBase( APTR handle ); VOID UnlockAmigaGuideBase( LONG key ); APTR OpenAmigaGuideA( struct NewAmigaGuide *nag, struct TagItem * ); APTR OpenAmigaGuide( struct NewAmigaGuide *nag, Tag tag1, ... );
Les prototypes sont les mêmes quels que soient le compilateur et le processeur/système cible (68k, PPC, x86, AmigaOS, PowerUp, WarpOS, MorphOS ou Amithlon).
Les pragmas
Les pragmas se trouvent généralement dans un répertoire "pragmas", ou parfois "pragma" (dans le cas d'Aztec C, et aussi lorsqu'on utilise fd2pragma (voir la fin de l'article)). Ils sont par définition spécifiques à chaque compilateur. Ils sont utilisés pour l'accès aux bibliothèques partagées par les compilateurs SAS/C, StormC 3, DCC, MaxonC, Lattice et Aztec.
Ces fichiers contiennent la description des registres du processeur utilisés pour transmettre les paramètres aux fonctions des bibliothèques, ainsi que la base de bibliothèque à utiliser.
Voici un exemple de pragma dans le format utilisé par SAS/C, DCC et Lattice:
#pragma libcall AmigaGuideBase LockAmigaGuideBase 24 801 #pragma libcall AmigaGuideBase UnlockAmigaGuideBase 2a 001 #pragma libcall AmigaGuideBase OpenAmigaGuideA 36 9802 #pragma tagcall AmigaGuideBase OpenAmigaGuide 36 9802
Vous n'y comprenez rien ? Moi non plus. Notez que le pragma tagcall n'est valable que pour SAS/C; les deux autres compilateurs ne peuvent apparemment pas appeler de fonctions à nombre variable d'arguments (aussi appelées tag functions) par cette méthode.
Voici la description des mêmes fonctions, mais pour StormC, Maxon et Aztec C cette fois:
#pragma amicall(AmigaGuideBase, 0x24, LockAmigaGuideBase(a0)) #pragma amicall(AmigaGuideBase, 0x2a, UnlockAmigaGuideBase(d0)) #pragma amicall(AmigaGuideBase, 0x36, OpenAmigaGuideA(a0,a1)) #pragma tagcall(AmigaGuideBase, 0x36, OpenAmigaGuide(a0,a1))
C'est déjà un petit peu plus compréhensible à l'oeil nu, puisqu'on y remarque la liste des registres utilisés. Comme précédemment, le pragma tagcall ne marche qu'avec StormC et pas avec Maxon ni Aztec C.
Les inlines
Les inlines se trouvent dans le répertoire "inline". Ce sont des macros qui remplissent exactement la même fonction que les pragmas mentionnés ci-dessus, mais pour les compilateurs GCC et VBCC (ainsi que StormC 4 puisqu'il utilise GCC).
Exemple, pour GCC:
#define LockAmigaGuideBase(handle) \
LP1(0x24, LONG, LockAmigaGuideBase, APTR, handle, a0, \
, AMIGAGUIDE_BASE_NAME)
#define UnlockAmigaGuideBase(key) \
LP1NR(0x2a, UnlockAmigaGuideBase, long, key, d0, \
, AMIGAGUIDE_BASE_NAME)
#define OpenAmigaGuideA(nag, attrs) \
LP2(0x36, APTR, OpenAmigaGuideA, struct NewAmigaGuide *, nag, a0, struct TagItem *, attrs, a1, \
, AMIGAGUIDE_BASE_NAME)
#define OpenAmigaGuide(a0, tags...) \
({ULONG _tags[] = { tags }; OpenAmigaGuideA((a0), (struct TagItem *)_tags);})
Je vous laisse décider si vous trouvez ça plus ou moins clair que les pragmas (mais rassurez-vous, ça doit être une question d'habitude). Notez que la syntaxe "({ ... })" n'est pas du C standard, mais est une notation spécifique à GCC qui sert à assigner au bloc la valeur de la dernière expression évaluée en son sein.
Et pour VBCC, ça ressemble à ça:
LONG __LockAmigaGuideBase(__reg("a0") APTR handle, __reg("a6") struct Library *)="\tjsr\t-36(a6)";
#define LockAmigaGuideBase(handle) __LockAmigaGuideBase((handle), AmigaGuideBase)
void __UnlockAmigaGuideBase(__reg("d0") long key, __reg("a6") struct Library *)="\tjsr\t-42(a6)";
#define UnlockAmigaGuideBase(key) __UnlockAmigaGuideBase((key), AmigaGuideBase)
APTR __OpenAmigaGuideA(__reg("a0") struct NewAmigaGuide * nag, __reg("a1") struct TagItem * *,
__reg("a6") struct Library *)="\tjsr\t-54(a6)";
#define OpenAmigaGuideA(nag, *) __OpenAmigaGuideA((nag), (*), AmigaGuideBase)
L'auteur de VBCC a donc opté pour l'assembleur inline (ce qui n'est pas du C standard non plus). Personnellement, j'aime bien, mais chacun ses goûts.
Ici aussi, OpenAmigaGuide(), qui a un nombre variable d'arguments, n'est pas présente. Mais cela ne veut pas dire qu'on ne peut pas l'utiliser... Il existe en effet, pour tous les compilateurs, la possibilité d'utiliser des stub libs.
Les stub libs
Les stub libs sont des bibliothèques statiques (ou link libraries) remplissant la même tâche que les fichiers pragmas et inlines. Elles sont utilisables par tous les compilateurs, mais chacun a bien sûr un format de libs différent (sinon, ça ne serait pas drôle).
Elles contiennent des stubs, qui sont des fonctions C standard se chargeant de prendre les paramètres sur la pile pour les placer dans les registres, avant d'appeler la fonction correspondante de la bibliothèque.
Vous avez généralement le choix entre utiliser des inlines ou pragmas et utiliser une stub lib.
Une chose à noter est que pour les compilateurs dont les inlines/pragmas ne gèrent pas les fonctions à nombre d'arguments variable, vous devez utiliser une stub-lib pour pouvoir appeler ces fonctions. Vous n'aurez normalement pas de souci avec les fonctions de l'OS, car des stubs devraient être inclus dans l'amiga.lib de votre compilateur, et donc linkées à vos exécutables par défaut.
Pour utiliser une stub lib, procédez de la même façon que pour n'importe-quelle link-lib: en rajoutant "-lnom_de_la_lib" à la ligne de commande, ou en rentrant son nom à l'endroit adéquat si vous utilisez une GUI comme celle de StormC. Attention, si le fichier s'appelle par exemple "socket.lib", il faut taper "-lsocket" sans le ".lib". GCC ainsi que VBCC PowerUP/MorphOS utilisent la notation "libsocket.a", mais ça ne change rien: il faut également taper "-lsocket".
Le répertoire proto
Dans ce répertoire, des fichiers comme par exemple "proto/amigaguide.h" qui incluent eux-mêmes:
- le fichier de prototypes du répertoire clib
- le fichier d'inlines ou de pragmas qui correspond au compilateur utilisé (ou rien si une stub lib doit être utilisée).
Vous ne devriez jamais inclure directement les pragmas ou inlines dans vos sources. De même, à l'exception de certains fichiers (comme clib/alib_protos.h qui contient les prototypes de l'amiga.lib), vous ne devriez jamais y inclure directement les fichiers clib.
Utilisez les fichiers du répertoire "proto" au lieu d'inclure explicitement les fichiers clib/pragmas/inlines de votre compilateur; de cette façon, vos sources seront compilables avec tous les compilateurs sans aucune modification (et en plus, ça fait deux fois moins de choses à taper).
Par exemple, ne tapez PAS ça:
#include <clib/amigaguide_protos.h> // c'est MAL #include <inline/amigaguide_protos.h> // c'est MAAAL
Mais au contraire tapez ça:
#include <proto/amigaguide.h>
Notez que les protos fournis dans les includes officielles de l'AmigaOS (dans le NDK 3.9 et sur les CD Dev) n'incluent pas les clib, pour une raison qui m'échappe. Mais peu importe: ils ne sont jamais utilisés; votre compilateur est normalement livré avec ses propres protos pour toutes les bibliothèques de l'OS.
L'amiga.lib
L'amiga.lib est une link lib à double emploi. Elle contient d'une part des fonctions utilitaires comme DoMethod(), HookEntry() ou RangeRand(), et d'autre part des stubs pour toutes les fonctions de l'OS, voire même celles de bibliothèques externes selon votre compilateur. C'est pourquoi même s'il vous manque le pragma ou l'inline pour une fonction donnée de l'OS, vous pourrez quand même l'utiliser grâce au stub présent dans l'amiga.lib (en général, cette lib est linkée par défaut dans tout exécutable par votre compilateur).
Pour utiliser des fonctions de l'amiga.lib dans un programme, vous devez inclure son clib:
#include <clib/alib_protos.h>
Ceci n'est pas nécessaire si vous n'avez besoin que des stubs et pas des fonctions utilitaires.
Les FD et SFD, et fd2pragma
Maintenant que vous avez vu la composition de tous ces fichiers, vous devez vous dire que ça va prendre du temps pour tous les taper à la main ;-). Heureusement, il existe des outils pour les générer automatiquement :-). Ces outils utilisent généralement les fichiers FD comme point de départ. Les fichiers SFD sont apparus au public assez récemment dans le NDK3.9 et permettent eux-mêmes de générer des FD, clib et autres fichiers.
Voici un petit bout d'un fichier FD:
LockAmigaGuideBase(handle)(a0) UnlockAmigaGuideBase(key)(d0) ##private amigaguidePrivate2()() ##public OpenAmigaGuideA(nag,*)(a0/a1)
Ce fichier contient les informations nécessaires pour que le compilateur sache comment appeler une fonction: son adresse par rapport à la base de la bibliothèque et les registres utilisés.
Il existe notamment fd2inline pour générer des inlines pour GCC à partir des FD, mais le plus puissant (et le seul que j'utilise) s'appelle fd2pragma et est disponible sur Aminet. Contrairement à ce que son nom pourrait laisser croire, il permet non seulement de générer des pragmas à partir de FD, mais également le contraire, ainsi que des inlines, stub libs, etc.; et ce pour tous les compilateurs et systèmes (AOS 68k, WOS, PUP, MOS - désolé, il manque Amithlon).
Voici quelques exemples:
- Pour générer un fichier "proto" qui marche avec tous les compilateurs:
- fd2pragma FD:intuition_lib.fd SPECIAL 35 TO INCLUDE:proto/
- (notez que fd2pragma suppose que les pragmas se trouvent dans le répertoire "pragma" (sans "s") lorsqu'il crée les protos.)
- Pour générer un fichier de pragmas qui marche avec tous les compilateurs:
- fd2pragma FD:intuition_lib.fd TO INCLUDE:pragma/
- Pour générer une stub lib WarpOS pour VBCC:
- fd2pragma FD:intuition_lib.fd CLIB INCLUDE:clib/intuition_protos.h SPECIAL 73 TO VLIBWOS:
Ces exemples utilisent l'intuition.library, mais vous n'aurez normalement jamais à générer des fichiers pour une bibliothèque de l'OS: tous les compilateurs sont livrés avec les fichiers nécessaires. En revanche, vous aurez peut-être besoin de créer des fichiers pour utiliser certaines bibliothèques disponibles sur Aminet ou ailleurs.
Je ne peux pas mentionner toutes les possibilités de fd2pragma ici, et vous laisse donc consulter sa documentation AmigaGuide. La partie "Beginner" contient des exemples pour générer des inlines/pragmas pour tous les compilateurs, et la partie "Useful example calls" en présente d'autres. Regardez la section "The important options" pour la liste des SPECIAL pour chaque compilateur. C'est en changeant cette valeur que vous choisirez quel fichier fd2pragma va générer.
Je vous conseille de toute façon de lire la doc consciencieusement :-) (attention, certaines parties sont plus à jour que d'autres).
La fin du début
Ainsi se termine cet article. J'espère que les exemples de fichiers inlines et pragmas ne vous ont pas trop rebutté; ils ne sont là que pour votre culture personnelle ;-). L'intérêt d'une bonne installation des includes est justement de pouvoir faire abstraction des différents mécanismes mis en oeuvre par les compilateurs, et de se concentrer sur l'important: l'utilisation des bibliothèques et la programmation !
