FAQ PascalConsultez toutes les FAQ
Nombre d'auteurs : 10, nombre de questions : 402, dernière mise à jour : 7 janvier 2018 Ajouter une question
Bienvenue dans la F.A.Q. Pascal !
Celle-ci rassemble les réponses aux questions les plus fréquemment posées sur le langage Pascal et tous ses outils de programmation. Si elle n'a pas pour vocation de répondre à toutes les interrogations possibles, elle reste une bonne base de connaissances sur le Pascal, et ne demande qu'à être enrichie par vos expériences personnelles.
Nous vous invitons à proposer vos propres questions/réponses directement dans la FAQ ou, si vous souhaitez apporter une modification à une question/réponse existante, à la poster dans le fil de discussion renseigné ci-dessous.
Nous vous souhaitons une bonne lecture !
L'équipe Pascal.
- Comment allouer plus de mémoire que les 640 kb de mémoire conventionnelle ?
- Comment réduire la mémoire utilisée par un programme ?
- Comment tester si un disque existe sur le système ?
- Comment détecter l'appui sur les touches de contrôle ?
- Comment détecter l'appui sur les touches F11 et F12 ?
- Que signifient appel far et appel near ?
- Comment détourner une interruption ?
- Comment inclure des instructions 32 bits dans un programme ?
- Comment récupérer en Assembleur un paramètre transmis par adresse ?
- Comment une fonction écrite en Assembleur peut-elle retourner une valeur ?
- Comment ajouter un module objet écrit en Assembleur à un programme ?
- Une variable définie dans un module objet peut-elle être utilisée dans le programme ?
- Comment inclure du code écrit en C dans un programme Pascal ?
Le gestionnaire de mémoire standard de Turbo Pascal ne peut accéder qu'à la mémoire conventionnelle, autrement dit aux 640 ko de mémoire basse. Or, de nos jours, les PC dépassent allègrement 1 Gb de mémoire vive. Comment dépasser la limite des 640 ko ?
La mémoire située au-delà de la mémoire conventionnelle est appelée mémoire haute ou mémoire étendue. Il existe deux protocoles sous Dos pour y accéder :
- EMS (Expanded Memory Specification), qui fonctionne avec un système de pages mémoire de 16 ko, plutôt utilisée sur les ordinateurs 286 et tombée en désuétude depuis ;
- XMS (eXtended Memory Specification), apparue avec les 386. On préférera cette dernière, qui permet d'accéder jusqu'à 64 Mo de mémoire.
Si ce n'est pas toute la mémoire dont vous disposez, c'est déjà une quantité plus que satisfaisante pour les besoins les plus courants.
Si vous désirez utiliser la mémoire XMS, il vous faudra créer vous-même l'interface entre celle-ci et votre programme, Turbo Pascal n'offrant rien de tel.
Vous pouvez aussi vous servir des unités ci-dessous.
Par défaut, un programme Turbo Pascal s'attribue toute la mémoire conventionnelle disponible (les 640 premiers kb). Si l'on désire laisser de la place pour d'autres programmes (typiquement lorsque l'on veut exécuter un programme externe), il est possible de jouer avec la directive de compilation {$M TaillePile,TasMini,TasMaxi} (en l'occurrence, la valeur de TasMaxi). Cette méthode, bien que préconisée par Borland, manque singulièrement de flexibilité, car la taille maximale du tas est fixée une fois pour toutes au moment de la compilation. Or, il est possible de réduire la taille du tas en cours d'exécution du programme.
Les deux procédures suivantes (HeapShrink et HeapExpand) sont toutefois à utiliser sous votre propre responsabilité. Celles-ci consistent à réduire la taille du tas au minimum possible (sans perdre vos variables allouées dynamiquement) avant l'exécution du programme externe (HeapShrink), puis à lui rendre sa taille originale au retour du programme externe (HeapExpand) :
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | Program Exemple; {$M 16384,0,655360} { On peut allouer toute la mémoire au démarrage } ... Procedure HeapShrink; { Réduit le tas au maximum } Var RegBx : Word; Begin RegBx := MemW[Seg(HeapPtr):Ofs(HeapPtr) + 2] - PrefixSeg; asm mov bx, RegBx mov es, PrefixSeg mov ah, 4Ah int 21h end; End; Procedure HeapExpand; { Redonne au tas sa taille originale } Var RegBx : Word; Begin RegBx := MemW[Seg(HeapEnd):Ofs(HeapEnd) + 2] - PrefixSeg; asm mov bx, RegBx mov es, PrefixSeg mov ah, 4Ah int 21h end; End; ... Begin ... HeapShrink; SwapVectors; Exec('COMMAND.COM',''); SwapVectors; HeapExpand; if DosError <> 0 then ... ... End. |
Pour tester si un disque existe, et donc s'il est accessible, on peut tenter de basculer sur ce disque et ensuite vérifier que tout s'est bien passé. Pour ce faire, on utilise les fonctions 0Eh et 19h de l'interruption 21h.
Le code suivant se sépare en plusieurs parties. Tout d'abord, on récupère le lecteur en cours, puis on tente de basculer sur le lecteur désiré et on compare avec la nouvelle valeur du lecteur en cours. Si cette nouvelle valeur diffère du lecteur désiré, alors c'est que ce lecteur n'existe pas et/ou n'est pas accessible. Enfin, on rétablit le lecteur d'origine.
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | Uses Dos; Function DriveExists (const Drive : Char) : Boolean; Var Regs : Registers; CurrentDrive, NewDrive : Byte; Begin DriveExists := False; { Test de validité } if not (Upcase(Drive) in ['A'..'Z']) then Exit; { On récupère le lecteur actuel } Regs.ah := $19; MsDos(Regs); CurrentDrive := Regs.al; { On bascule sur le nouveau lecteur } NewDrive := Ord(UpCase(Drive)) - Ord('A'); Regs.ah := $0E; Regs.dl := NewDrive; MsDos(Regs); { On vérifie que tout s'est bien déroulé } Regs.ah := $19; MsDos(Regs); DriveExists := (Regs.al = NewDrive); { On réactive le lecteur précédent } Regs.ah := $0E; Regs.dl := CurrentDrive; MsDos(Regs); End; |
Les touches de contrôle : Ctrl, Alt, AltGr ou Maj, possèdent une place à part sur notre clavier. Leur manière de détection – mis à part au niveau de l'interruption clavier – est totalement différente de celle des autres touches.
On peut compter également dans les rangs des touches de contrôle les touches à bascule : Verr Num, Verr Maj, Inser ou Arrêt Défil.
Pour savoir si une de ces touches est actuellement pressée (active) ou relâchée (inactive), il faut regarder la valeur du Word à l'adresse mémoire $0040:$0017. Chaque bit de ce mot est attribué à une touche. Si elle est pressée (ou active), le bit est à 1, sinon, il est à 0. Il est à noter que, si les touches de contrôle sont en double (une gauche et une droite), alors elles sont gérées séparément. Le tableau suivant les décrit :
Touche | Bit(s) | Masque |
Ctrl gauche | 2 et 8 | $0104 |
Ctrl droite | 2 | $0004 |
Maj gauche | 1 | $0002 |
Maj droite | 0 | $0001 |
Alt | 3 et 9 | $0208 |
Alt Gr | 3 | $0008 |
Verr Num | 5 | $20 |
Verr Maj | 6 | $40 |
Inser | 7 | $80 |
Arrêt Défil | 4 | $10 |
Les touches à bascule n'utilisent que le Byte à l'adresse $0040:$0017.
L'exemple suivant se charge de vérifier la combinaison de touches AltGr + E. Si ces deux touches sont pressées, alors le mot « Euro » est affiché à l'écran. Attention ! La combinaison Alt+E correspond à une touche étendue. Mais tel quel, avec seulement ReadKey, il est impossible de savoir s'il s'agit juste de Alt ou bien AltGr. C'est ici qu'intervient notre code. On va distinguer Alt et AltGr. Si AltGr est pressé, alors seul le bit 3 est actif. Sinon, il y a aussi le bit 9. On va donc vérifier que le bit 3 est actif, mais pas le 9... Pour ce faire, on applique le masque isolant les bits 3 et 9, et on regarde si le bit 9 est bien éteint, alors que le 3 ne l'est pas.
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Program TestEuro; Uses Crt; Var KbdState : Word absolute $0040:$0017; { Déclare une variable Word à l'adresse $0040:$0017 } Ch : Char; Begin repeat Ch := ReadKey; if Ch = #0 then { Alt+E renvoie la combinaison #0, #18 } begin if (ReadKey = #18) and (KbdState and $0208 = $0008) then { Masques associés à Alt et AltGr } WriteLn('Euro'); end; until Ch = #27; { On quitte avec Echap } End. |
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | Var KbdSwitch : Byte absolute $0040:$0017; OldSwitch : Word; Begin (* Active Ver Num *) OldSwitch := KbdSwitch; KbdSwitch := OldSwitch and (not $20) or $20; { On ne modifie que Verr Num, pas les autres bits } { On modifie les LED du clavier en fonction des bits } Port[$60] := $ED; Port[$60] := (KbdSwitch shr 4) and 7; (* Désactive Verr Num *) OldSwitch := KbdSwitch; KbdSwitch := OldSwitch and (not $20); { On conserve les autres bits, sauf Verr Num } { On modifie les LED du clavier en fonction des bits } Port[$60] := $ED; Port[$60] := (KbdSwitch shr 4) and 7; End; |
Turbo Pascal ne sait malheureusement pas détecter les touches F11 et F12. Il faut l'aider un peu... Pour cela, il faut se servir de la fonction 10h de l'interruption 16h :
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | Uses Dos; Function ReadExtKey : Word; Var Regs : Registers; KbdStatus : Byte absolute $0040:$0096; Begin ReadExtKey := 0; if KbdStatus and 16 = 0 then Exit; Regs.ah := $10; Intr($16, Regs); ReadExtKey := Regs.ax End; Var Key : Word; Begin ... Key := ReadExtKey; if Key = $8500 then { F11 appuyée } else if Key = $8600 then { F12 appuyée }; ... End. |
Chaque programme EXE conçu avec Turbo Pascal est composé de plusieurs segments : des segments de code, un segment de données et un segment de pile. De plus, Turbo Pascal place toujours chaque module (programme principal ou unité) dans un segment distinct. Bien entendu, il y a communication entre chacun des segments de code, notamment pour appeler des procédures et fonctions.
C'est ici qu'intervient la notion d'appel « far » et d'appel « near ». En effet, sitôt que l'on entre dans une procédure, il faut aussi penser à en sortir. Par conséquent, on sauve sur la pile la position actuelle avant d'appeler la procédure. Si on reste dans le même segment, seul l'offset est à empiler. On parle alors d'appel « near » (proche). Si par contre, on change de segment, alors il faut penser également à empiler le segment dans lequel on se trouve, et on parle d'appel « far » (lointain).
De fait, comme une unité se situera toujours dans un segment différent du programme principal ou d'une autre unité, toutes ses procédures et fonctions déclarées dans la partie interface seront déclarées comme « far ». De même, comme une procédure ou une fonction déclarée dans le programme principal n'est accessible que dans ce même programme principal, elle sera toujours déclarée comme « near ».
Un problème se pose donc quand une procédure est déclarée dans le programme principal et qu'elle est destinée à être utilisée en dehors de celui-ci. C'est notamment le cas des gestionnaires d'interruption. Dans ce cas, il faut forcer Turbo Pascal à adopter un mode d'appel « far ».
Pour ce faire, deux solutions s'offrent à nous. Soit on ajoute la directive « far » à l'en-tête de la procédure, soit on la place entre les directives de compilation {$F+}...{$F-} :
Code delphi : | Sélectionner tout |
1 2 3 4 5 | Procedure AppelFar; far; Begin ... End; |
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 | {$F+} Procedure AppelFar; Begin ... End; {$F-} |
Il peut être utile d'écrire sa propre routine d'interruption pour remplacer une routine du BIOS ou du DOS.
Il s'agit en fait d'une procédure classique affublée de la directive interrupt. Important : cette procédure doit être déclarée comme FAR, à l'aide de la directive de compilation {$F+}.
Pour que votre routine personnelle remplace une des routines du BIOS ou du DOS, il faut aller déposer son adresse dans le vecteur d'interruptions, en prenant soin de sauvegarder l'adresse qui s'y trouvait. Les procédures GetIntVec et SetIntVec, de l'unité Dos, permettent respectivement de lire et d'écrire dans le vecteur d'interruptions.
Important : avant la terminaison de votre programme, il faut impérativement restaurer le vecteur d'interruptions dans son état initial, sous peine de risquer de planter la machine (ou la machine DOS virtuelle).
Prenons un exemple : la désactivation du Ctrl+C et du Ctrl-Break. Pour cela, il suffit de remplacer l'adresse des entrées 23h et 1Bh dans le vecteur d'interruptions par l'adresse d'une routine personnelle qui ne fait rien :
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | Uses Dos; Var { Pour sauver les anciens vecteurs d'interruption } OldInt1B, OldInt23 : Pointer; { L'interruption nulle qui va remplacer les vecteurs actuels } {$F+} Procedure NullInt (Flags, CS, IP, AX, BX, CX, DX, SI, DI, DS, ES, BP: Word); interrupt; Begin End; {$F-} Begin { On sauve les anciens vecteurs } GetIntVec($1B,OldInt1B); GetIntVec($23,OldInt23); { On installe la procédure nulle } SetIntVec($1B,@NullInt); SetIntVec($23,@NullInt); ... { Ici, Ctrl+Break est désactivé } ... { On rétablit les anciens vecteurs } SetIntVec($1B,OldInt1B); SetIntVec($23,OldInt23); end. |
Les interruptions et leurs gestionnaires, par Eric Sigoillot
Turbo Pascal ayant été conçu dans les années 80, il ne sait pas explicitement utiliser les instructions 32 bits des processeurs 386 et supérieurs.
Pour bien comprendre le principe qui suit, il faut descendre jusqu'au code machine.
Considérons les deux instructions suivantes :
Code : | Sélectionner tout |
1 2 | mov ax, cx |
Code : | Sélectionner tout |
1 2 | mov eax, ecx |
Code : | Sélectionner tout |
1 2 | 89h C8h |
Code : | Sélectionner tout |
1 2 | 66h 89h C8h |
Ainsi, pour coder mov eax, ecx, on écrira :
Code delphi : | Sélectionner tout |
1 2 3 4 | asm db 66h; mov ax, cx end; |
De fait, pour coder mov eax, 0, on écrira :
Code delphi : | Sélectionner tout |
1 2 3 4 | asm db 66h; mov ax, 0; dw 0000h end; |
Pour accéder à un paramètre par adresse (transmis avec var), vous devrez toujours utiliser un pointeur, en général ES:DI ou bien DS:SI :
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 | Procedure ParamAdresse (var P : Integer); assembler; asm ... les di,P ... end; |
Si votre fonction n'utilise qu'un bloc en assembleur, alors vous pouvez vous servir de la variable locale déclarée implicitement @Result :
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | Function Test : Integer; Begin ... asm ... mov @Result, ax ... end; ... End; |
Type du résultat | Méthode de renvoi |
Type scalaire 8 bits (entier, caractère, booléen, énuméré) | Registre AL |
Type scalaire 16 bits (Integer, Word) | Registre AX |
Type scalaire 32 bits (LongInt, pointeur) | Registres DX:AX |
String | @Result (cas particulier) |
Real | Registres DX:BX:AX |
Autres types de réels (Single, Double...) | Registre ST(0) (premier registre FPU) |
Il est possible d'écrire des routines en Assembleur, de les compiler sous forme de fichier objet (.OBJ) et de les lier à un programme Turbo Pascal. Toutefois, il faut respecter plusieurs règles :
- le module objet doit être au format .OBJ 16 bits ;
- les procédures et fonctions doivent se trouver dans un segment appelé CODE ou CSeg ;
- pour pouvoir être utilisées dans le programme Pascal, ces procédures et fonctions doivent être déclarées PUBLIC ;
- les éventuelles variables doivent se trouver dans un segment appelé DATA ou DSeg ;
- pour utiliser des variables, procédures ou fonctions du programme Pascal, il faut les référencer par la directive EXTRN.
Voici un exemple de source Assembleur (TCURSOR.ASM, compilé dans le fichier objet TCURSOR.OBJ) :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | DATA SEGMENT WORD PUBLIC EXTRN Textx : word EXTRN Texty : word EXTRN Textpage : word DATA ENDS CODE SEGMENT WORD PUBLIC ASSUME CS:CODE,DS:DATA PUBLIC TCURSOR COMMENT ! Déplacement du curseur vers les coordonnées (textx,texty) ! TCURSOR PROC FAR push ax push bx push dx pushf mov bx,Textpage mov bh,bl ; BH = page active mov dx,Textx ; DL = colonne mov ax,Texty mov dh,al ; DH = ligne mov ah,2 ; Déplacement du curseur int 10h ; Services vidéo du BIOS popf pop dx pop bx pop ax ret TCURSOR ENDP CODE ENDS END |
Code delphi : | Sélectionner tout |
1 2 3 | {$L TCURSOR} Procedure TCURSOR; external; |
La réponse est non : même si, dans votre module objet (.OBJ), vous définissez une variable dans le segment DATA (ou DSeg), même si cette variable est finalement stockée avec les variables globales du programme, elle ne peut être utilisée ailleurs que dans les routines du module objet. La solution est de déclarer cette variable dans le programme Pascal et, dans le module objet, de la déclarer dans le segment DATA avec la directive EXTRN.
Il est impossible d'intégrer directement du code en C dans un programme Pascal. Toutefois, il est possible de « ruser ». Pour ce faire, il faut créer en aval un module en C et le compiler sous forme de fichier objet compatible Intel *.OBJ.
Certaines règles doivent cependant s'appliquer – et il est impossible d'y déroger :
- le module en C doit être compilé sous un modèle de mémoire small ;
- le module C doit être compilé avec un compilateur 16 bits si le compilateur Pascal est un compilateur 16 bits et 32 bits si le compilateur Pascal est 32 bits ;
- toutes les données (variables, constantes, types, classes...) du module C seront inaccessibles dans le code Pascal, mais, par contre, les variables du module Pascal seront accessibles depuis le module C. Par conséquent, toute donnée devant être partagée devra être déclarée dans le module Pascal ;
- il faut déclarer toutes les routines destinées à être partagées en mode d'appel far ;
- il est a priori impossible d'utiliser des routines de la runtime library (RTL) C/C++ à cause de conflit de noms.
Une fois toutes ces remarques prises en considération, il suffit de créer un module en C, en déclarant en tant que extern tous les éléments se situant dans le module Pascal, en prenant garde à la casse (majuscules et minuscules). Il faut ensuite le compiler sous forme de fichier objet.
Dans le module Pascal, il suffit de déclarer toutes les procédures et variables nécessaires puis d'intégrer au code le fichier objet grâce à la directive de compilation {$L MODULE_C.OBJ} où MODULE_C correspond au nom de votre module C.
L'exemple suivant illustre l'intégration d'un module C dans un programme en Pascal.
Code C : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /* Module MODC.C */ typedef unsigned int word; extern void Clear(word Color); /* Procédure du module Pascal */ extern word Color; /* Variable déclarée dans le module Pascal */ void CallClear() { /* Modifie une variable Pascal */ if (Color == 15) Color = 1; else Color++; /* Appelle une procédure Pascal */ Clear(Color); } |
Code delphi : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | Program IntegrationC; Cses Crt; { Variable partagée } Var Color : Word; { Procédure partagée } Procedure Clear (Color : Word); Begin TextBackground(Color); ClrScr; End; { Intégration de la procédure en C } Procedure CallClear; external; {$L MODC.OBJ} Begin Color := 1; CallClear; ReadLn; End. |
Proposer une nouvelle réponse sur la FAQ
Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour çaLes sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.