ASL Library et GFA Basic
Sous AmigaOS, la bibliothèque ASL (Application Support Library) affiche une boite de dialogue pour choisir des fichiers, des modes d'écran et des typographies. Cette bibliothèque fut judicieusement implémentée dans le Kickstart 2.0 en 1990, puis mise à jour à chaque évolution du Workbench, jusqu'au dernier Kickstart 3.2.3 sorti en 2025.

Le Blitz Basic, le HiSoft BASIC, AQB et le PureBasic fournissent dans leurs exemples d'installation, des codes de requêtes ASL prêts à l'emploi.

Hélas, le GFA Basic ne reconnait pas l'asl.library. Pour sélectionner des fichiers, il existe la commande FILESELECT mais elle est peu ergonomique et a un style ancien.

Mon amour pour ce langage obsolète étant ignoré par la raison, je décide de créer un code 100% GFA pour lancer des requêtes ASL. Je m'appuie sur sa syntaxe "moderne" qui intègre beaucoup de routines du système et permet de gérer les pointeurs.
Création de la procédure
Je commence par créer une procédure aslreq() avec quelques paramètres afin que ce code puisse être réutilisable dans n'importe quel autre programme GFA Basic.
@aslreq("Choisis ton fichier","OK","Cancel","","RAM:")
PROCEDURE aslreq(titre$,oktexte$,canceltexte$,fichierdep$,dossierdep$)
...
RETURN
Ouverture de l'asl.library
J'ouvre l'asl.library avec la fonction système OpenLibrary() gérée nativement par le GFA, puis je récupère en retour l'adresse de base ASL dans la variable aslbase% :
aslname$="asl.library"+CHR$(0)
aslbase%=OpenLibrary(V:aslname$,0)
Le nom de la bibliothèque doit se terminer par un caractère nul qui correspond au code ASCII numéro 0 indiquant la fin d'une chaîne de caractères. Il faut donc concaténer un CHR$(0) à la chaîne "asl.library".
Si l'ajout du CHR$(0) vous semble un odieux bricolage, le GFA Basic propose l'instruction CHAR{} permettant de lire ou écrire un texte au format C en ponctuant les textes d'un octet nul :
aslname$="asl.library"
CHAR{aslname$}=aslname$
aslbase%=OpenLibrary(V:aslname$,0)
Dans le premier paramètre d'OpenLibrary(), V: désigne le pointeur de la variable aslname$.
Structure FileRequester
Vient ensuite l'allocation d'une structure FileRequester dans laquelle sont indiqués les tags, à savoir le titre de la fenêtre de la requête, le texte du bouton OK, le texte du bouton Cancel, le fichier et le dossier initiaux. Ces indications sont transmises via les paramètres de ma fonction GFA aslreq().
Le GFA Basic ne gérant pas les structures, elle est reconstituée dans la mémoire en se documentant sur le fichier libraries/asl.h des Includes et Autodocs. En voici un extrait permettant de définir les tags utilisés dans le code GFA :
/************************************************************************/
/* Tags for AllocAslRequest() and AslRequest() */
/************************************************************************/
#define ASL_Dummy (TAG_USER + 0x80000)
#define ASL_Hail ASL_Dummy+1 /* Hailing text follows */
#define ASL_Window ASL_Dummy+2 /* Parent window for IDCMP & screen */
#define ASL_LeftEdge ASL_Dummy+3 /* Initialize LeftEdge */
#define ASL_TopEdge ASL_Dummy+4 /* Initialize TopEdge */
#define ASL_Width ASL_Dummy+5
#define ASL_Height ASL_Dummy+6
#define ASL_HookFunc ASL_Dummy+7 /* Hook function pointer */
/* Tags specific to file request */
#define ASL_File ASL_Dummy+8 /* Initial name of file follows */
#define ASL_Dir ASL_Dummy+9 /* Initial string of filerequest dir */
/* Tags specific to font request */
#define ASL_FontName ASL_Dummy+10 /* Initial font name */
#define ASL_FontHeight ASL_Dummy+11 /* Initial font height */
#define ASL_FontStyles ASL_Dummy+12 /* Initial font styles */
#define ASL_FontFlags ASL_Dummy+13 /* Initial font flags for textattr */
#define ASL_FrontPen ASL_Dummy+14 /* Initial frontpen color */
#define ASL_BackPen ASL_Dummy+15 /* Initial backpen color */
#define ASL_MinHeight ASL_Dummy+16 /* Minimum font height to display */
#define ASL_MaxHeight ASL_Dummy+17 /* Max font height to display */
#define ASL_OKText ASL_Dummy+18 /* Text displayed in OK gadget */
#define ASL_CancelText ASL_Dummy+19 /* Text displayed in CANCEL gadget */
Une Taglist est un tableau de paires : tag-valeur. Le tag est un entier de 32 bits. La valeur a également une taille de 32 bits. Il peut s'agir d'un entier ou d'un pointeur. La Taglist doit se terminer par le tag 0 (Fin de liste). Pour établir la Taglist, je choisis certains tags dans l'ordre que je souhaite :
| Tag (4 octets) | Valeur (4 octets) |
|---|---|
| ASL_Hail | Pointeur vers "Choisis ton fichier" |
| ASL_OKText | Pointeur vers "OK" |
| ASL_CancelText | Pointeur vers "Cancel" |
| ASL_File | Pointeur vers "" |
| ASL_Dir | Pointeur vers "RAM:" |
| TAG_END | 0 |
Pour commencer, je réserve une zone de mémoire de 88 octets remplis de zéros (paramètre 65536). La variable tags% reçoit l'adresse de départ de la zone réservée :
tags%=MALLOC(88,65536)
Pour déterminer l'adresse de base des tags de l'ASL requester j'additionne le TAG_USER qui vaut &H80000000 et l'offset des tags de l'ASL requester qui vaut &H80000. La somme des deux donne &H80080000. Le TAG_USER est défini dans utility/tagitem.h et vaut ((ULONG)(1UL<<31)), ce qui fait &H80000000 en hexa (1 décalé de 31 bits vers la gauche). J'écris donc en GFA :
tagasl%=&H80080000
En référence à l'include asl.h, j'écris chaque tag-valeur pour personnaliser la boite de dialogue ASL :
LONG{tags%}=tagasl%+1 ! Titre du requester (ASL_Hail)
titre$=titre$+CHR$(0)
LONG{tags%+4}=V:titre$
LONG{tags%+8}=tagasl%+18 ! Texte OK (ASL_OKText)
oktexte$=oktexte$+CHR$(0)
LONG{tags%+12}=V:oktexte$
LONG{tags%+16}=tagasl%+19 ! Texte Cancel (ASL_CancelText)
canceltexte$=canceltexte$+CHR$(0)
LONG{tags%+20}=V:canceltexte$
LONG{tags%+24}=tagasl%+8 ! Fichier de départ (ASL_File)
fichierdep$=fichierdep$+CHR$(0)
LONG{tags%+28}=V:fichierdep$
LONG{tags%+32}=tagasl%+9 ! Dossier de départ (ASL_Dir)
dossierdep$=dossierdep$+CHR$(0)
LONG{tags%+36}=V:dossierdep$
LONG{tags%+40}=0 ! Fin de liste
En GFA Basic, LONG{adr%}=x% écrit la valeur de la variable x% sur 4 octets à l'adresse adr%.
Après avoir renseigné les tags, j'écris un pointeur vers l'endroit où la liste des tags est stockée dans registre a0 qui correspond à regs%(8) :
regs%(8)=tags%
Appel des fonctions AllocAslRequest() et AslRequest()
La fonction système AllocAslRequest() n'est pas gérée nativement par le GFA. Je vais donc l'appeler à l'aide de l'instruction GFA Basic RCALL dont la syntaxe est :
RCALL adresse, reg%()
L'instruction RCALL stocke certaines valeurs dans les registres du CPU avant de lancer une routine assembleur, puis de tester le contenu des registres après exécution de la routine. J'utilise un tableau de 16 entiers de 4 octets : reg%(). Avant le lancement de la routine, les entrées du tableau sont copiées dans les registres, et après exécution de la routine, le contenu des registres est écrit dans les éléments correspondants du tableau :
| Registres de données | d0 à d7 dans reg%(0) à reg%(7) |
| Registres d'adresse | a0 à a6 dans reg%(8) à reg%(14) |
| User Stack Pointer | a7 dans reg%(15) (retour seulement) |
Pour appeler une fonction d'une bibliothèque système, je dois placer l'adresse de base de la bibliothèque dans le registre a6 qui correspond à regs%(14) :
regs%(14)=aslbase%
Sous AmigaOS, l'espace d'adressage d'une bibliothèque peut se faire dans les deux sens, ce qui permet l'usage d'un seul pointeur (Library Base). L'adressage avec un décalage négatif permet d'appeler les fonctions de la bibliothèque.
Une fois qu'une application a ouvert une bibliothèque, elle peut commencer à utiliser ses fonctions. Pour accéder à une fonction, une application a besoin de l'adresse de base de la bibliothèque renvoyée par OpenLibrary() et du décalage du vecteur de bibliothèque (Library Vector Offset) de la fonction. Le LVO d'une fonction est le décalage entre l'adresse de base de la bibliothèque et le vecteur de la fonction. Un LVO est un nombre négatif car les vecteurs précèdent la base de la bibliothèque en mémoire.
Voici les offsets indiqués dans le ROM Kernel Manual Includes And Autodocs :
***************** asl
* "asl.library"
##base _AslBase
##bias 30
##public
*--- functions in V36 or higher (distributed as Release 2.0) ---
*
30 $ffe2 -$001e AllocFileRequest()()
36 $ffdc -$0024 FreeFileRequest(fileReq)(a0)
42 $ffd6 -$002a RequestFile(fileReq)(a0)
48 $ffd0 -$0030 AllocAslRequest(type,tagList)(d0/a0)
54 $ffca -$0036 FreeAslRequest(request)(a0)
60 $ffc4 -$003c AslRequest(request,tagList)(a0/a1)
##end
D'après les indications du RKM ci-dessus, la fonction AllocAslRequest() est accessible à l'offset -48 en décimal c'est à dire -&H30 en hexa. J'écris donc en GFA Basic :
RCALL aslbase%-&H30,regs%()
Je lance AllocASLRequest() pour réserver une zone mémoire qui servira à inscrire la sélection de l'utilisateur.
La boite de dialogue ASL va finalement apparaître grâce à la fonction AslRequest() qui est appelée avec l'instruction RCALL car cette fonction n'est pas gérée nativement par le GFA.
RCALL aslbase%-&H3C,regs%()

Quand l'utilisateur valide sa sélection, le nom du fichier et du dossier sont écrits dans la mémoire. A l'offset 4, j'ai un pointeur vers le nom du fichier, et à l'offset 8, un pointeur vers le chemin. Le résultat du requester est mis dans le registre d0 correspondant à regs%(0). C'est ce que je teste pour savoir si l'utilisateur à cliqué sur cancel.
IF regs%(0)
' L'utilisateur a choisi un fichier
fichier$=CHAR{LONG{monfilereq%+4}}
chemin$=CHAR{LONG{monfilereq%+8}}
PRINT "Fichier sélectionné : ";fichier$
PRINT "Chemin : ";chemin$
PAUSE 100
ENDIF
Le fichier et le dossier sélectionnés seront ensuite lus avec l'instruction LONG puis publiés dans une fenêtre avec l'incontournable PRINT. C'est du Basic, n'oublions pas !

Le code final
Le code complet fonctionne aussi bien en mode interprété que compilé. J'ai tenté de l'écrire de la façon la plus concise possible afin de ne pas trop rougir de la comparaison avec les autres langages.
' ASL Requester en GFA Basic par Guillaume Guittenit
' Ouverture d'une fenêtre
OPENW #5
@aslreq("Choisis ton fichier","OK","Cancel","","RAM:")
PROCEDURE aslreq(titre$,oktexte$,canceltexte$,fichierdep$,dossierdep$)
' Ouverture de la asl.library
aslname$="asl.library"+CHR$(0)
aslbase%=OpenLibrary(V:aslname$,0)
IF aslbase%=0
PRINT "Impossible d'ouvrir la asl.library"
END
ENDIF
' Allocation d'une structure FileRequester
tags%=MALLOC(88,65536) ! Réserve 88 octets remplis de zéros
tagasl%=&H80080000
LONG{tags%}=tagasl%+1 ! Titre du requester ASL
titre$=titre$+CHR$(0)
LONG{tags%+4}=V:titre$
LONG{tags%+8}=tagasl%+18 ! Texte OK
oktexte$=oktexte$+CHR$(0)
LONG{tags%+12}=V:oktexte$
LONG{tags%+16}=tagasl%+19 ! Texte Cancel
canceltexte$=canceltexte$+CHR$(0)
LONG{tags%+20}=V:canceltexte$
LONG{tags%+24}=tagasl%+8 ! Fichier de départ
fichierdep$=fichierdep$+CHR$(0)
LONG{tags%+28}=V:fichierdep$
LONG{tags%+32}=tagasl%+9 ! Dossier de départ
dossierdep$=dossierdep$+CHR$(0)
LONG{tags%+36}=V:dossierdep$
LONG{tags%+40}=0 ! Fin de liste
' Dimensionnement du tableau de registres pour l'instruction RCALL
DIM regs%(15)
regs%(8)=tags% ! Registre A0
regs%(14)=aslbase% ! Base dans le registre A6
RCALL aslbase%-&H30,regs%()
monfilereq%=regs%(0)
' Appel du sélecteur de fichiers
regs%(8)=regs%(0)
RCALL aslbase%-&H3C,regs%()
' Le résultat est lu. Si on a zéro, l'utilisateur a sélectionné Cancel
IF regs%(0)
' L'utilisateur a choisi un fichier
fichier$=CHAR{LONG{monfilereq%+4}}
chemin$=CHAR{LONG{monfilereq%+8}}
PRINT "Fichier sélectionné : ";fichier$
PRINT "Chemin : ";chemin$
PAUSE 100
ENDIF
RETURN
' Fermeture de la asl.library et libération de la mémoire
~CloseLibrary(aslbase%)
~MFREE(tags%,88)
CLOSEW #5
Les fichiers sont disponibles ici :
Résultat
Le code interprété ou compilé fonctionne nickel sur mon Amiga 1200 avec l'OS 3.2.3 :

Le code 68k compilé fonctionne également sous MorphOS. Merci à son compilateur Trance Just In Time :

