L’assembleur en QuickBasic (ASM)
Auteur : Asher256
Mise à jour : Avril 2003
URL : http://qbworld.asher256.com
Table des matières :
- 1- Introduction
- 2- Avant toute chose : TASM
- 3- Méthode pour compiler un fichier *.ASM en fichiers *.LIB et *.QLB
- 4- Comment appeler des fonctions en assembleur depuis QuickBasic ?
- 5- Comment passer des arguments à une fonction en assembleur ?
- 6- Que signifie le mot-clé BYVAL en QuickBasic ?
- 7- Comment retourner une variable de type LONG ?
- 8- Pourquoi ne pas utiliser l’assembleur NASM ?
- 9- Conclusion
1- Introduction :
Il est toujours très utile d’intégrer des routines en assembleur dans vos jeux vidéo, utilitaires, etc.
La création d’une bibliothèque en assembleur pour optimiser les performances est indispensable dans de nombreux cas. Ce document n’a pas pour vocation de vous enseigner le langage assembleur, mais vous expliquera comment concevoir des routines (PROC) pour les exécuter directement depuis votre programme QuickBasic.
Pour s’approprier pleinement ce document, le lecteur doit posséder de bonnes bases en assembleur (segments, offsets, pile…), ainsi qu’une très bonne maîtrise de QuickBasic et de l’environnement MS-DOS.
2- Avant toute chose : TASM
TASM est un excellent assembleur 16 bits. C’est l’outil que nous utiliserons dans ce tutoriel, bien que vous puissiez également recourir à MASM de Microsoft ou à l’excellent NASM.
La seule manière de lier QuickBasic et l’assembleur repose sur l’utilisation des fichiers objets (.OBJ). Après avoir assemblé votre code source avec TASM pour obtenir un fichier *.OBJ, vous utiliserez les utilitaires BC.EXE et LINK.EXE (fournis avec QuickBasic) pour compiler le fichier *.OBJ en bibliothèques *.LIB et *.QLB.*
Pour intégrer votre code assembleur à votre programme *.BAS, il vous suffira de lancer l’environnement via la commande QB.EXE /L asm.qlb. Bien que cette démarche puisse paraître complexe de prime abord, vous constaterez qu’elle est en réalité très simple !
3- Méthode pour compiler un fichier *.ASM en fichiers *.LIB et *.QLB :
Vous devez disposer de TASM (TASM.EXE), de LINK.EXE, ainsi que de l’environnement QuickBasic 4.5 accompagné de ses utilitaires dédiés, LIB.EXE et LINK.EXE :
Prenons pour exemple le fichier assembleur suivant :
lib.asm
Assemblez-le d’abord en fichier objet (lib.obj) avec TASM, puis générez les fichiers lib.qlb and lib.lib. Les fichiers *.QLB permettent d’exécuter vos routines au sein de l’interpréteur QuickBasic, tandis que les fichiers *.LIB sont requis lors de la compilation finale en exécutable (*.EXE).
Voici les commandes MS-DOS pour générer les fichiers .QLB et .LIB (il est recommandé de créer un fichier de commandes de type Batch pour automatiser cette tâche) :
del lib.lib
TASM lib.asm
LIB lib.lib +lib.obj
LINK /qu lib.lib,lib.qlb,NULL,c:\qb45\bqlb45.lib
La première commande supprime le fichier lib.lib préexistant, puis TASM assemble lib.asm en lib.obj.
Ensuite, l’utilitaire LIB.EXE de QuickBasic intègre le fichier lib.obj dans la bibliothèque lib.lib (en la créant si elle n’existe pas).
Enfin, LINK.EXE convertit le fichier lib.lib en fichier de chargement rapide lib.qlb. Veillez à remplacer c:\qb45 par le chemin réel de votre répertoire QuickBasic.
IMPORTANT : Si vous utilisez un fichier Batch, n’oubliez pas de préfixer les commandes TASM, LIB et LINK par l’instruction CALL.
Voyons désormais comment structurer nos premières routines et comment retourner une valeur de type INTEGER dans une fonction QuickBasic…
4- Comment appeler des fonctions en assembleur depuis QuickBasic ?
Une solide compréhension de l’assembleur est nécessaire pour aborder les notions suivantes.
Afin de garantir la compatibilité avec QuickBasic, veillez à toujours respecter la structure de départ suivante lors de la création de votre fichier assembleur :
.MODEL MEDIUM , BASIC ; Modèle compatible avec QuickBasic
.STACK 100h ; Allocation de l'espace de pile (256 octets)
.DATA ; Section des variables
variable DB 'salut !!!!!!!!!'
.CODE
; Vos routines PROC ici
END
Créons une routine sous forme de fonction qui retourne la valeur 10 à QuickBasic. Voici notre premier fichier source assembleur, test.asm :
'------------------------------------- TEST.ASM ----------------------------------
.MODEL MEDIUM , BASIC ; Modèle compatible avec QuickBasic
.STACK 100h ; Allocation de l'espace de pile (256 octets)
.DATA ; Section des variables
.CODE
PUBLIC salut ; La fonction est déclarée publique pour être visible par QuickBasic
salut PROC FAR ; Toujours utiliser la directive PROC FAR
MOV AX,10 ; Par convention, QuickBasic récupère la valeur contenue dans le registre AX comme résultat
RET ; Ne jamais omettre l'instruction RET
salut ENDP
END
'------------------------------------- TEST.ASM -----------------------------------
Une fois le fichier test.asm assemblé en bibliothèques test.lib et test.qlb selon la méthode précédente, lancez QuickBasic via la commande qb.exe /L test.qlb.
Puis, codez l’appel au sein du fichier test.bas :
'------------------------------------- TEST.BAS ----------------------------------
DECLARE FUNCTION salut% ( ) ' La fonction est déclarée avec le suffixe % (INTEGER) car le registre AX est sur 16 bits
DIM variable AS INTEGER
variable = salut ' Appel de la fonction salut
PRINT variable ' Affichage du résultat
'------------------------------------- TEST.BAS ----------------------------------
Appuyez sur F5
À l’exécution, le programme affichera la valeur 10 !
En résumé, pour retourner une valeur de type INTEGER, il suffit de charger cette valeur dans le registre AX.
5- Comment passer des arguments à une fonction en assembleur ?
En assembleur, il est impossible de déclarer le passage de variables de haut niveau au sein de la directive PROC comme on le ferait en langage évolué :
PUBLIC salut
salut (var1,var2) PROC FAR
; Instructions
RET
salut ENDP
Cette syntaxe est incorrecte. En réalité, QuickBasic transmet l’adresse mémoire de chaque argument en l’empilant dans la pile (STACK). Prenons pour exemple la déclaration suivante :
DECLARE SUB salut( var1 AS INTEGER, var2 AS INTEGER, var3 AS INTEGER)
QuickBasic empile les adresses respectives de var1, var2 et var3. Pour accéder à ces données depuis votre routine assembleur, vous devez initialiser un pointeur de pile stable au début de votre procédure à l’aide du registre BP :
PROC SALUT
PUSH BP ; Sauvegarde du registre BP sur la pile
MOV BP,SP ; Initialisation du pointeur de cadre de pile
; Instructions
POP BP ; Restauration du registre BP
RET nombre_octets_passés ; Libération de l'espace occupé par les arguments
ENDP
NOTE : L’instruction RET doit impérativement être suivie du nombre total d’octets occupés par les arguments sur la pile. Dans notre exemple, avec 3 variables de type INTEGER (2 octets chacune), nous utiliserons RET 6.
L’adresse de var3 se situera à l’emplacement [BP+06], celle de var2 à [BP+08] et celle de var1 à [BP+10]. Voici comment charger la valeur de var1 dans le registre AX :
PROC SALUT
PUSH BP ; Sauvegarde du registre BP sur la pile
MOV BP, SP ; Initialisation du pointeur de cadre de pile
; Instructions de la routine
MOV bx, [BP+10] ; Chargement de l'adresse de var1 dans BX
MOV ax, [bx] ; Chargement de la valeur pointée par BX dans AX
POP bp ; Restauration du registre BP
RET 6 ; Libération des 6 octets occupés par les trois arguments (3 * 2 octets)
ENDP
Voici l’organisation théorique de la pile (STACK) lors de l’exécution de la routine :
10 Adresse de var1
08 Adresse de var2
06 Adresse de var3
04 BASIC RETURN SEGMENT
02 BASIC RETURN OFFSET
00 BP
Si votre routine nécessite la sauvegarde du registre de segment DS (via un PUSH DS), l’organisation de la pile sera décalée de deux octets :
12 Adresse de var1
10 Adresse de var2
08 Adresse de var3
06 BASIC RETURN SEGMENT
04 BASIC RETURN OFFSET
02 BP
00 DS
Le registre DS étant empilé après BP, toutes les adresses relatives se trouvent décalées. L’adresse de var1 devient alors accessible via la position [BP+12].
La modification de la valeur d’un argument s’effectue tout aussi simplement que sa lecture :
MOV BX, [BP+10] ; Chargement de l'adresse de var1 dans BX
MOV [BX], 20 ; Écriture de la valeur 20 à l'adresse pointée par BX
6- Que signifie le mot-clé BYVAL en QuickBasic ?
Modifions notre déclaraction de la manière suivante :
DECLARE SUB salut( BYVAL var1 AS INTEGER, BYVAL var2 AS INTEGER, BYVAL var3 AS INTEGER)
Quel est le rôle du modificateur BYVAL placé devant les arguments ?
Le mot-clé BYVAL indique à QuickBasic de transmettre directement la valeur numérique de l’argument au lieu de son adresse mémoire. Cela vous permet de lire la variable directement depuis la pile, sans avoir à récupérer son adresse au préalable dans un registre d’index comme BX.
Par exemple, pour obtenir directement la valeur de var1 (INTEGER) dans AX :
MOV AX, [BP+10]
Vous accédez ainsi directement à la donnée. Attention cependant : sous cette configuration, il est impossible de modifier la variable d’origine dans le programme QuickBasic, puisque seule sa valeur numérique a été transmise, et non son adresse.
7- Comment retourner une variable de type entier long (LONG) ? (contribution de Strix Slayer)
Cette opération est également très accessible. Alors qu’un entier classique (16 bits) se retourne dans le registre AX, un entier long (32 bits) utilise une paire de registres : le mot de poids fort est placé dans DX, et le mot de poids faible dans AX.
Reprenons l’exemple précédent :
Le fichier : Toto.asm
PUBLIC Toto
PROC Toto
MOV DX, 00h ; Chargement du mot de poids fort dans DX
MOV AX, 5 ; Chargement du mot de poids faible dans AX
RETF
ENDP
Dans l’environnement QuickBasic, chargez la bibliothèque *.QLB correspondante et codez l’appel dans le fichier Toto.bas :
Le fichier: Toto.bas
DECLARE FUNCTION Toto&
PRINT Toto&
Le programme affichera alors la valeur 5 !
8- Pourquoi ne pas utiliser l’assembleur NASM ? (contribution de Strix Slayer
Il est tout à fait possible d’utiliser l’assembleur NASM pour développer des routines exploitables sous QuickBasic. La mise en œuvre est particulièrement simple. En en-tête de votre fichier source, il vous suffit de spécifier :
[BITS 16]
Aucune autre configuration n’est requise. La déclaration d’une fonction s’effectue tout aussi simplement :
Ma_Fonction:
.....
[Votre code assembleur ici]
.....
RETF
Il suffit ensuite d’indiquer au compilateur que cette fonction doit être accessible globalement via la directive suivante :
GLOBAL Ma_Fonction
Et le tour est joué !
Seule contrainte syntaxique : la directive GLOBAL Ma_Fonction doit impérativement précéder l’étiquette de la fonction (Ma_Fonction:).
Vous constatez que la mise en œuvre est très accessible.
Pour générer le fichier objet compatible (*.OBJ), exécutez simplement la commande MS-DOS suivante :
NASM monfichier.asm -fobj
Les étapes de liaison ultérieures restent identiques à celles décrites pour TASM.
Voici un court exemple récapitulatif illustrant cette méthode :
Le fichier Toto.asm :
[BITS 16]
GLOBAL Toto
Toto:
MOV AX, 5
RETF
Le fichier Toto.bas :
DECLARE FUNCTION Toto%
PRINT Toto%
9- Conclusion :
Ce document vous a présenté les fondements techniques nécessaires pour associer le langage assembleur à QuickBasic et concevoir vos propres bibliothèques d’optimisation.
Vous avez découvert comment transmettre des arguments de type INTEGER ou LONG, déclarer et appeler des fonctions de bas niveau, modifier la valeur d’une variable passée par référence, ou encore optimiser les accès mémoire grâce au passage par valeur avec la directive BYVAL.
Si ce tutoriel vous a accompagné efficacement dans votre apprentissage, n’hésitez pas à m’adresser vos retours d’expérience et d’encouragement par e-mail.
Bonne programmation et à bientôt !

