[EN] [IT]
[INFO] [ACQUÉRIR] [PLAN] [RESSOURCES]
CHAPITRE
APPRENDRE À PROGRAMMER AVEC FUTUREBASIC
NOTES

Les branchements IV

Arguments et registres

Leçon 13 : Passage des paramètres d'une fonction

Utilisation des registres
Il faut être honnête, le passage des paramètres n'est pas toujours aussi aisé qu'il a été montré jusqu'ici. Cela n'est pas dû à FutureBASIC qui nous rend au contraire les choses plus simples, mais à l'architecture des processeurs. Si vous compilez votre programme pour le PowerPC, FutureBASIC utilisera des registres du processeur (des cases spéciales) dans lesquelles il y rangera des valeurs. L'intérêt de ces registres est tout simplement de permettre au processeur de travailler beaucoup plus rapidement, car accéder aux variables dans des registres demande moins de temps que d'aller les retrouver dans la mémoire vive de l'ordinateur. Lorsque l'on doit exécuter de longues boucles, la différence est perceptible. Aussi, bien que l'utilisation des registres soit optionnelle, vous ne manquerez pas de vouloir en profiter vous aussi.

Les variables placées ainsi en registre n'ont pas d'adresse en mémoire et on commence à entrapercevoir les problèmes : comment travailler sur des adresses-mémoire avec des variables qui n'en ont pas ?

S'ajoute à cette difficulté pressentie, le fait que le Compilateur stockera toutes les variables qu'il peut ranger dans des registres au moment où une fonction est appelée jusqu'à ce qu'il n'y ait plus un seul registre disponible. Il va commencer par y ranger les paramètres qui sont passés à la fonction à l'exclusion des chaînes de caractères et des variables en simple et double précision. Il y rangera ensuite les variables locales déclarées à l'intérieur de la fonction si elles ne demandent pas plus de 4 octets de stockage (taille maximum qui peut être contenue dans un registre), jusqu'à épuisement des 8 registres utilisateur disponibles, après quoi, toutes les variables locales suivantes sont stockées en mémoire.

Notez dès à présent que les variables à virgule flottante disposent de registres particuliers; le programmeur peut contrôler la mise en registre de ce type de variable de la façon suivante :

LOCAL
DIM AS DOUBLE
x,y
LOCAL FN maFonction
  DIM AS DOUBLE w,z
END FN


Les variables x et y déclarées entre les mots-clés LOCAL et LOCAL FN seront placées dans les registres pour les flottants, tandis que les variables w et z déclarées dans le corps de la fonction seront localisées en mémoire adressable.

Bon nombre de commandes FutureBASIC, mais aussi d'appels à la Toolbox et pourquoi pas de vos fonctions-utilisateur auront besoin de modifier directement les valeurs de variables qui leur sont passées en paramètres, de ce fait, les variables concernées doivent avoir une adresse en mémoire, c'est-à-dire un emplacement où la fonction pourra stocker une nouvelle valeur le cas échéant. Lors de la compilation de votre programme, FutureBASIC détecte si une variable placée en registre est utilisée dans une instruction qui attend une variable nécessitant une adresse en mémoire. Il vous signalera l'erreur et arrêtera la compilation. Le remède le plus immédiat à cette situation est de forcer le Compilateur à créer la variable en mémoire adressable, ce qui se fait très simplement avec l'instruction DIM.

LOCAL FN maFonction
  DIM @ x AS SHORT
  DIM @ y AS HANDLE
  DIM   z AS SHORT
  DIM   w AS LONG
  DIM   r AS RECT
// instructions
END FN


Grâce au symbole @ dans les instructions ci-dessus, les variables x et y sont créées en mémoire adressable, tandis que z et w seront placées dans des registres, s'il en reste de disponibles. Les variables de type record ne sont jamais placées dans des registres, donc dans l'exemple ci-dessus, la variable r (ici une structure RECT) n'aboutira jamais dans un registre.

L'ordre dans lequel le Compilateur rencontre dans votre code source les variables à créer a une importance dans la mise en registre des variables. Les paramètres de la fonction sont d'abord placés en registre, ensuite viennent les variables déclarées par l'instruction DIM. Il est donc conseillé de déclarer au plus tôt les variables qui sont le plus souvent sollicitées dans le corps de la fonction.

Supposez que vous vouliez écrire une fonction qui modifie la valeur d'une variable que vous lui passez en paramètre depuis une autre fonction locale, par exemple :

LOCAL FN ajouterUn(@pointeurVariable AS PTR)
  pointeurVariable.nil%++
END FN

LOCAL FN tester
  DIM AS INT x, i

  FOR
i = 30 TO 20 STEP -1
    FN AjouterUn(x)
    PRINT x
  NEXT
END FN


// Programme principal
FN tester


Ce petit bout de code recèle deux pièges auxquels vous serez très souvent confrontés. Tout d'abord, la variable x sera créée dans un registre, en conséquence le Compilateur ne pourra pas faire son travail pour la fonction ajouterUn puisqu'il lui sera impossible de résoudre l'adresse en mémoire de la variable. Vous aurez donc droit à un message d'erreur, vous signalant que la variable x ne peut pas être placée en registre. On a vu qu'il était simple de régler le problème, il suffit d'utiliser le symbole @ avec l'instruction DIM, éventuellement de la manière suivante :

DIM AS INT
i, @ x

Notez simplement que dans l'instruction ci-dessus, la variable i sera placée en registre, tandis que x sera créée en mémoire adressable et que si vous déclarez d'autres variables sur la même ligne d'instruction après la variable x, elles seront également placées en mémoire.

Ce code acceptera de compiler, mais ne vous donnera peut-être pas le résultat que vous escomptez. C'est l'occasion de revoir ici que déclaration de variable et initialisation sont deux choses bien distinctes. La déclaration réserve une place en mémoire (ou bien réserve un registre) mais n'altère pas la valeur précédemment contenue à cette adresse (ou dans ce registre), si bien que la variable peut contenir toute valeur qui "traînait" là avant sa création. Il faut donc expressément donner une valeur initiale à votre variable x, c'est-à-dire l'initialiser pour obtenir des valeurs prédictibles.

Pourquoi FutureBASIC n'initialise-t-il pas à 0 toutes les variables nouvellement créées comme il le fait avec les variables globales ?

L'initialisation des variables consomme du temps. Bien sûr, dans une fonction appelée de manière isolée qui pourrait éventuellement manipuler un grand nombre de variables locales, cela ne serait pas vraiment perceptible, mais si maintenant vous devez appeler des centaines de milliers de fois des dizaines de fonctions comportant des dizaines de variables dans une boucle de votre programme, vous commencerez à percevoir la différence. Notez que les variables globales n'ont besoin d'être initialisées qu'une seule fois au démarrage du programme et que FutureBASIC s'en charge en mettant toutes les variables globales à 0. Dans beaucoup d'applications, pour ne pas dire la majorité, la rapidité maximale est souvent ce qui est recherchée. Deux possibilités s'offrent à vous pour régler le problème de l'initialisation des variables locales :

- vous pouvez forcer FutureBASIC à initialiser à leur valeur nulle toutes les variables qui seront utilisées dans le corps de la fonction à l'aide du mot-clé CLEAR :

CLEAR LOCAL FN maFonction
END FN


Ou encore :

CLEAR LOCAL
LOCAL FN
maFonction
END FN


Ceci est valable pour tous les types de variables y compris les chaînes de caractères et les records.

- vous pouvez vous assurer que le flux du programme passera nécessairement par une instruction qui assignera une valeur connue aux variables utilisées. Cela revient à initialiser manuellement et individuellement chacune des variables. Dans notre exemple, il nous suffirait de donner une valeur quelconque (et pourquoi pas 0 ?) à notre variable x avant de l'utiliser dans des instructions qui vont tenir compte de sa précédente valeur. Voici donc le programme corrigé :

LOCAL FN ajouterUn(@pointeurVariable AS PTR)
  pointeurVariable.nil%++
END FN

LOCAL FN
tester
  DIM AS INT i,@ x

  x = 0
  FOR i = 30 TO 20 STEP -1
    FN ajouterUn(x)
    PRINT x
  NEXT
END FN


// Programme principal
FN tester


Il est important de bien garder à l'esprit cette différence entre la déclaration et l'initialisation, car vous pourriez être chanceux et avoir un programme fautif qui semblera fonctionner correctement pendant un certain temps, puis tout d'un coup se mettra à produire des résultats inattendus en vous laissant perplexe pour un long moment.

Dans l'exemple ci-dessus, nous retrouvons la forme syntaxique "adresse+point+décalage+identificateur de type" que nous avons déjà expliquée. Ici, nous utilisons le symbole % puisque nous voulons ranger une valeur sur un entier court. Notez toutefois que FutureBASIC dispose d'une commande qui incrémente d'une unité la variable que vous lui passez en paramètre INC(variable) qui est équivalent à variable = variable + 1. Le langage dispose également de mots-clés spécifiques lorsque vous manipulez des adresses plutôt que des variables : INC BYTE(adresse), INC WORD(adresse) et INC LONG(adresse) qui se passent de commentaires. Bien entendu DEC BYTE, DEC WORD et DEC LONG sont aussi disponibles. Mais, sachez que la forme d'écriture que nous avons précédemment employée vous permet de manipuler de même des nombres à virgule flottante et des chaînes de caractères en utilisant le suffixe adéquat, c'est pourquoi nous la privilégierons. Exemple :

LOCAL FN AssignerChaine(@pointeurVariable AS PTR)
  pointeurVariable.nil$ = "Je suis une chaîne"
END FN

LOCAL FN
tester
  DIM AS STR255 variable

  FN AssignerChaine(variable)
  PRINT variable
END FN

// Programme principal
FN tester


Le passage des paramètres aux fonctions est une notion à comprendre très tôt pour se sentir parfaitement à l'aise avec FutureBASIC, malheureusement, il recèle encore quelques difficultés pour arriver à la maîtrise.

On a vu que les paramètres sont, a priori, passés par valeur. C'est-à-dire que si vous passez une variable en paramètre à une fonction, c'est son contenu (sa valeur) qui est transmis. En réalité, ceci n'est vrai que pour les variables courantes qui ont jusqu'à 4 octets de stockage, mais FutureBASIC s'arrange pour que ce principe s'applique également aux chaînes de caractères et aux nombres à virgule flottante. Les records, les containers et les tableaux ne bénéficient pas de ce traitement de faveur et FutureBASIC transmet systématiquement l'adresse de telles variables lorsque vous les utilisez dans la liste des paramètres des appels à une fonction. Prenons le cas le plus simple, vous souhaitez passer une variable record de type RECT à une fonction locale :

DIM r AS RECT
r.top = 10 : r.left = 10 : r.bottom = 50 : r.right = 50
FN afficheRectangle(r)


FutureBASIC ne peut pas transmettre le contenu d'une telle structure, ce qu'il va faire alors c'est transmettre de lui-même l'adresse de la variable à la place. Cela a deux conséquences : tout d'abord, la fonction qui recevra le paramètre doit être définie de manière appropriée du fait qu'elle réceptionnera un pointeur :

LOCAL FN afficheRectangle(adresseRectangle AS PTR)
END FN


En second lieu, cela implique que vous ne travaillerez pas avec une copie de la variable qui a été passée en paramètre mais bien avec l'originale; toute modification des champs du record à l'intérieur de la fonction locale affecte donc la variable d'origine. Si pour une raison quelconque vous ne souhaitez pas altérer la variable originale, il vous faudra faire une copie des valeurs qu'elle contient dans une variable locale du même type. La copie de ces données se réalise au moyen de l'instruction BLOCKMOVE qui prend trois paramètres : l'adresse source où se trouvent les données que l'on veut dupliquer, l'adresse de destination où les octets copiés seront rangés et enfin le nombre d'octets à copier. La commande BLOCKMOVE est bien mal nommée, car elle pourrait laisser entendre que les données sont déplacées de bloc en bloc, or elle en fait bel et bien une copie en laissant les données d'origine en place. Voici une façon possible de programmer ce mécanisme :

LOCAL FN afficheRectangle(adresseRectangle AS PTR)
  DIM rectangleLocal AS RECT

  BLOCKMOVE
adresseRectangle,@rectangleLocal,SIZEOF(rectangleLocal)
END FN


Il faut noter tout de suite que certains programmeurs affectionnent beaucoup le raccourci syntaxique, optimisé pour des petites quantité d'octets à copier, que propose FutureBASIC pour réaliser la même opération :

LOCAL FN afficheRectangle(adresseRectangle AS PTR)
  DIM rectangleLocal AS RECT

  rectangleLocal;8 = adresseRectangle
END FN


La valeur après le point virgule indique le nombre d'octets à copier, cette valeur doit être une valeur littérale comme dans l'exemple ci-dessus, ou bien une constante nommée; elle ne peut pas être le résultat d'un calcul, ni d'une fonction comme la fonction SIZEOF que nous avons utilisée précédemment.

Il y a enfin, une façon plus simple d'arriver au même résultat, et sans doute aura-t-elle votre préférence. FutureBASIC permet d'affecter un type à un pointeur, si bien que vous pouvez indiquer, dans la liste des arguments formels de la fonction, qu'un pointeur d'un type donné sera réceptionné. Vous informerez de cette façon le Compilateur sur la quantité d'octets qu'il doit s'attendre à trouver à l'adresse qui aura été transmise à la fonction, il pourra alors faciliter votre écriture de la façon suivante :

LOCAL FN afficheRectangle(adresseRectangle AS PTR TO RECT)
  DIM rectangleLocal AS RECT

  rectangleLocal = adresseRectangle
END FN


Dans l'exemple ci-dessus, FutureBASIC réalise automatiquement pour vous la copie des octets en quantité appropriée. Cette facilité d'écriture est bien pratique, mais elle pourrait être un tant soit peu trompeuse, car elle n'est pas symétrique et vous ne pourrez pas écrire ce qui suit, pour le cas où vous souhaiteriez copier à nouveau les données de votre variable locale vers l'adresse contenue dans la variable pointeur :

adresseRectangle = rectangleLocal

adresseRectangle est un pointeur, c'est-à-dire qu'il contient une adresse mémoire occupant 4 octets (taille d'un entier long) tandis que la variable rectangleLocal est une structure (ici de 8 octets), vous devez donc dans ce cas utiliser explicitement la commande BLOCKMOVE pour copier les données d'un endroit à un autre de la mémoire :

BLOCKMOVE @rectangleLocal,adresseRectangle,SIZEOF(RECT)

Une difficulté supplémentaire peut survenir avec le passage en paramètre d'un record à une fonction locale si vous souhaitez utiliser la structure avec un appel de la Toolbox qui attend une variable. Dans la fonction locale, vous ne disposez que de l'adresse de la structure qui aura été réceptionnée dans un paramètre entrant, ce paramètre est un pointeur qui sera placé en registre. Par ailleurs, FutureBASIC contrôle les paramètres passés aux appels de la Toolbox, et, de fait, lorsqu'une procédure ou une fonction de la Toolbox attend une variable, FutureBASIC s'assure de retrouver son adresse, car en effet, dans certains cas les appels de la Toolbox doivent pouvoir modifier le contenu des variables et pour que l'opération puisse avoir lieu il faut que ces variables soient localisées en mémoire adressable. Ainsi si vous passez directement le pointeur que vous avez reçu, le Compilateur se plaindra en vous signalant qu'une variable en registre ne peut être utilisée avec ce type d'instruction. Examinez, ce petit bout de code d'une fonction qui afficherait un rectangle à l'aide de la procédure FrameRect qui attend une variable de type RECT en paramètre :

LOCAL FN afficheRectangle(adresseRectangle AS ^RECT)
  FrameRect(adresseRectangle)
END FN


Ce code ne pourra pas être compilé, le Compilateur vous signalera un peu maladroitement qu'il est impossible d'utiliser une variable en registre avec cette instruction. Notez que si vous désactivez la mise en registre des variables via les préférences, le code pourra être compilé mais ne fonctionnera pas mieux. En réalité, FutureBASIC essaie de résoudre l'adresse de la variable afin d'invoquer la procédure de la Toolbox (FrameRect accepte en paramètre une variable de type RECT), or vous ne disposez pas d'une variable de ce type mais d'un pointeur sur une variable de ce type, vous avez, en somme, déjà l'adresse de la structure que FutureBASIC essaie de déterminer. Que faire alors ?

Le problème n'est pas sans issue, et la première alternative est de déclarer une variable locale de type RECT et d'y copier les octets depuis l'adresse du pointeur entrant, comme dans les exemples précédemment étudiés. Vous passez ensuite la variable locale à la fonction de la Toolbox et le tour est joué.

LOCAL FN afficheRectangle(adresseRectangle AS ^RECT)
  DIM rectangleLocal AS RECT

  rectangleLocal = adresseRectangle
  FrameRect(rectangleLocal)
END FN


Il y a cependant une possibilité plus directe en demandant à FutureBASIC de ne pas contrôler le paramètre que vous envoyez à la procédure de la Toolbox de la façon suivante :

LOCAL FN afficheRectangle(adresseRectangle AS ^RECT)
  FrameRect(#adresseRectangle)
END FN


L'utilisation du symbole # demande à FutureBASIC de ne pas contrôler la validité du paramètre qui est transmis et d'utiliser sa valeur en tant qu'adresse à envoyer à la Toolbox.

Cette technique peut-être utilisée lorsque vous ne disposez pas d'une variable à envoyer à un appel de la Toolbox qui en attend une, cela peut arriver par exemple lorsque vous souhaitez envoyer à la fonction une adresse en mémoire qui est le résultat d'un calcul. Exemple :

FrameRect(#adresseConnue + 10)

Si dans un premier temps, vous butez trop souvent sur des problèmes dûs à l'utilisation des registres, n'oubliez pas que vous pouvez désactiver entièrement cette option dans les préférences de l'Editeur à l'onglet variables (toutes vos variables seront alors créées en mémoire adressable), mais je ne suis pas enclin à vous encourager dans cette voie-là, essayez plutôt de persévérer pour vous habituer très tôt à tenir compte de ce facteur dans votre façon de programmer.

Il y a encore deux choses à savoir concernant le typage des pointeurs lorsqu'ils figurent dans la liste des arguments formels d'une fonction. Tout d'abord, le Compilateur n'a aucun moyen de savoir si ce que vous écrivez est correct ou pas concernant le type que vous attribuez au pointeur. En réalité, vous pouvez considérer l'adresse interceptée en paramètre comme n'importe quel type de pointeur. C'est vous, en tant que programmeur, qui indiquez à FutureBASIC la manière dont vous voulez "percevoir" les données rangées à l'adresse en question. C'est en quelque sorte un gabarit que vous appliquez arbitrairement à une zone en mémoire, et dans certaines circonstances cela peut parfois s'avérer utile, car vous aurez ainsi la possibilité de "découper" les données suivant les champs d'un record qui n'est pas du type original de la variable passée en paramètre. Par ailleurs, si vous utilisez le typage de pointeur, vous pourrez vous dispenser des identificateurs de type pour les champs du record, ce qui n'est pas le cas avec un pointeur générique. Si vous utilisez un pointeur générique (adresse AS PTR), FutureBASIC n'a aucun moyen de savoir à quelle structure vous vous référez, et vous devez utiliser des décalages au moyen de constantes pour accéder aux champs du record. Dans ce cas, il vous faut utiliser un identificateur de type pour indiquer au Compilateur le nombre d'octets que vous souhaitez lire ou ranger à l'emplacement mémoire concerné. Exemple :

LOCAL FN translaterRectangle(adresseRectangle AS PTR)
  adresseRectangle.left%  += 10
  adresseRectangle.right% += 10
END FN


Avec un pointeur générique, ci-dessus, vous indiquez les identificateurs de type pour les champs du record; les décalages en mémoire sont calculés à partir de constantes _left et _right prédéfinies par FB.

LOCAL FN translaterRectangle(adresseRectangle AS ^RECT)
  adresseRectangle.left  += 10
  adresseRectangle.right += 10
END FN


En typant le pointeur, vous pouvez vous passer des identificateurs de type pour les champs du record, car le Compilateur sait alors de quelle structure vous voulez parler, et partant il peut déterminer l'espace de stockage de chacun des champs de la structure.

Si, comme on le voit, il est possible de passer des records aux fonctions (en fait leur adresse), il n'est en revanche pas possible de retourner un record à une fonction appelante. Pour dire la vérité cela semble "marcher" dans bien des cas, mais il n'est pas garanti que cela "marche" à tous les coups. L'écriture ci-dessous n'est pas recommandée et sera considérée comme une erreur de programmation :

LOCAL FN
creerRectangle
  DIM rectangleLocal AS RECT

  rectangleLocal.top    = 10
  rectangleLocal.left   = 15
  rectangleLocal.bottom = 20
  rectangleLocal.right  = 25
END FN = rectangleLocal

LOCAL FN
SyntaxeNonRecommandee
  DIM rectangle AS RECT

  rectangle = FN creerRectangle
  PRINT rectangle.top
  PRINT rectangle.left
  PRINT rectangle.bottom
  PRINT rectangle.right
END FN

// Programme principal
FN SyntaxeNonRecommandee


En principe, les variables d'une fonction locale n'existent plus après que la fonction a terminé son exécution (rectangleLocal n'aura plus d'existence à la fin de la fonction creerRectangle, souvenez vous que les variables disparaissent et que les blocs qui contiennent leur valeur sont libérés), mais il se trouve que si vous retournez une variable record, ce qui en principe n'est pas autorisé, FutureBASIC renvoie son adresse (tout comme il sait déjà réceptionner l'adresse d'une structure que vous passez en paramètre à une fonction locale) et si vous utilisez une variable proprement dimensionnée pour récupérer le résultat de la fonction, il copiera gracieusement les octets qui sont rangés à l'adresse retournée par la fonction. Vous n'avez pas de garantie que cela fonctionnera à tous les coups.

On vient de le voir, le passage des paramètres à une fonction est un aspect de la programmation qui n'est pas toujours facile à réaliser, c'est pourtant un point essentiel auquel vous serez immédiatement confronté dans la mesure où, comme il a déjà été dit, les routines GOSUB/RETURN ont depuis très longtemps été abandonnées par les programmeurs qui utilisent FutureBASIC. En vérité, si vous concevez correctement la nature d'une variable, les choses restent cohérentes et finalement simples. Vous devez garder à l'esprit la distinction entre la variable elle-même et son contenu  :

  • Une variable a un nom symbolique (ex : maVariable) auquel vous vous référez pour la manipuler dans vos instructions.

  • Une variable a un type (ex : INT) qui indique sa dimension, c'est-à-dire la quantité d'espace de stockage (en octets) dont elle a besoin pour stocker les valeurs qu'elle est amenée à contenir.

  • Une variable a une adresse (@maVariable) on parle souvent aussi de son pointeur, qui est un nombre (sur un entier long) indiquant l'emplacement physique en mémoire qui contiendra sa valeur. On a vu aussi, qu'avec le PowerPC, le contenu de certaines variables pouvait être stocké dans des cases spéciales ou registres et que de fait, ces variables n'avaient pas d'adresse en mémoire.

  • Une variable a une valeur qui est composée d'un certain nombre d'octets rangés à l'adresse physique de la variable.

    Les choses ont tendance à se compliquer dès que l'on parle de pointeurs et de handles.

    Un pointeur est une variable dont le contenu est une adresse en mémoire. (ex : unPointeur = @maVariable). Les pointeurs ont la taille d'un entier long (4 octets). Les blocs qu'ils référencent sont inamovibles.

    Un handle est une variable pointeur dont le contenu est l'adresse d'un autre pointeur qui indique à son tour l'adresse d'un bloc en mémoire. Au gré de ses besoins, le Memory Manager du Macintosh (le Gestionnaire de mémoire), peut avoir à déplacer le bloc à une autre adresse en mémoire, et si cela doit arriver, il se charge aussi de mettre à jour le handle, pour que l'adresse indiquée par le handle pointe bien sur le nouvel emplacement du bloc.

    En dépit de son apparente complexité, l'utilisation des handles est quasiment indispensable pour les projets conséquents. FutureBASIC propose, pour faciliter la vie aux débutants, les variables du type CONTAINER. Fort heureusement, et je vous rassure tout de suite les containers sont plus difficiles à expliquer qu'à utiliser. Un container n'est autre qu'un pointeur dont le contenu est un handle, c'est à dire un autre pointeur qui référence un autre pointeur encore. Cette triple indirection peut donner le tournis, mais en réalité la manipulation des containers est presque d'une simplicité enfantine pour le programmeur tout en mettant à sa disposition les avantages procurés par les handles.

    On a déjà évoqué succinctement une limitation des containers (dont les tableaux dynamiques souffrent aussi), c'est ici l'occasion d'en reparler : cette limitation tient au fait que, de par leur nature, ces types de variables ont une portée globale; et en conséquence elles doivent être déclarées dans un bloc structurel BEGIN GLOBALS/END GLOBALS. Cela implique surtout que l'on ne peut déclarer de containers (ni de tableaux dynamiques) à l'intérieur d'une fonction locale, on ne peut pas plus retourner ce type de variable à une fonction appelante.

    On peut bien-sûr travailler avec des containers dans des fonctions locales puisqu'elles "voient" les variables globales du programme à condition qu'elles ne soient pas en LOCAL MODE :

    BEGIN GLOBALS
      DIM
    gC AS CONTAINER
    END GLOBALS

    LOCAL FN
    remplirContainer
      gC = "Chaîne rangée dans le container"
    END FN

    // Programme principal
    FN remplirContainer
    PRINT gC


    Jusqu'ici, il n'y a rien de spécial à signaler, mais maintenant que nous connaissons la nature intime des containers, nous pouvons éventuellement écrire une fonction en LOCAL MODE, totalement indépendante du reste du programme et de ses variables globales. Bien entendu, cela demande beaucoup plus de travail de notre part, cela demande aussi de connaître la Toolbox du Macintosh, mais cela reste néanmoins possible. Voici une nouvelle version pour l'exemple précédent :

    BEGIN GLOBALS
      DIM
    gC AS CONTAINER
    END GLOBALS

    LOCAL MODE
    LOCAL FN
    remplirContainer(@CPtr AS PTR)
      DIM chaine AS STR255

      chaine = "chaîne rangée dans le container"
      IF CPtr.nil& = _nil THEN CPtr.nil& = FN NewHandle(0)
      LONG IF CPtr.nil&
        LONG IF FN PtrToXHand(@chaine[1],CPtr.nil&,chaine[0]) <> _noErr
          DisposeHandle(CPtr.nil&) : CPtr.nil& = _nil
        END IF
      END IF
    END FN


    // Programme principal
    FN remplirContainer(gC)
    PRINT gC


    Ci-dessus, la fonction remplirContainer intercepte l'adresse de la variable container qui lui est passée en paramètre. Nous savons qu'à cette adresse est stocké un handle, nous pouvons donc l'extraire pour l'utiliser avec les fonctions de la Toolbox qui manipulent des handles. Pour connaître la valeur du handle, il nous faut lire un entier long à l'adresse du pointeur, la syntaxe "pointeur+point+décalage+identificateur de type" est utilisée ici, nous aurions pu tout aussi bien écrire PEEK LONG (CPtr) ou encore [CPtr] qui permettent aussi de lire un entier long à une adresse en mémoire. Nous commençons par tester si un handle est déjà présent à cette adresse, si nous trouvons une valeur égale à 0, cela signifie que notre variable container n'a pas encore été initialisée (FutureBASIC se charge d'initialiser pour nous les containers quand il le faut). Si nous ne trouvons pas de handle, nous en créons un à l'aide de la fonction de la Toolbox NewHandle tout comme le ferait FutureBASIC à notre place. Remarquez que l'on peut demander un nouvel handle pointant sur un bloc d'une taille de 0 octet ! On peut en effet, très facilement agrandir la taille d'un bloc relogeable par la suite si cela est nécessaire. C'est d'ailleurs là un des grands atouts des handles.

    La fonction NewHandle retourne le handle nouvellement créé ou bien 0 (on utilise souvent la constante _nil à la place de la valeur 0) si le Memory Manager n'a pu réaliser l'opération. La valeur retournée par la fonction de la Toolbox est stockée directement à l'adresse du pointeur.

    Après quoi, nous testons si la valeur du handle n'est pas nulle. Il faut impérativement s'assurer que les handles sont valides avant de les utiliser dans une quelconque instruction, sinon gare à la casse.

    Si nous disposons effectivement d'un handle, nous essayons alors de copier le contenu d'une chaîne de caractère dans le bloc référencé par le handle. Nous utilisons ici la fonction de la Toolbox PtrToXHand qui transfère les octets depuis une adresse en mémoire vers un handle en remplaçant ce qu'il y avait déjà dans le bloc de mémoire relogeable. La fonction accepte trois paramètres, l'adresse mémoire où commencent les octets à copier, le handle qui pointe sur le bloc relogeable où les octets seront transférés et le nombre d'octets à copier, et elle retourne un code d'erreur. Nous passons donc l'adresse du premier caractère de la chaîne en omettant l'octet de longueur, car ici nous ne sommes intéressés que par la suite de caractères, nous passons le handle que nous avons récemment stocké à l'adresse du pointeur et enfin le nombre de caractères de la chaîne à copier.

    Si la fonction échoue, elle retourne un code d'erreur, et dans ce cas-là nous prenons des dispositions, à savoir ici, nous libérons le bloc réservé pour le handle avec la procédure de la Toolbox DisposeHandle et nous remettons à 0 la valeur du handle stockée à l'adresse de notre pointeur. Notez que la procédure DisposeHandle ne remet pas à 0 la variable que vous lui passez en paramètre, elle ne fait que libérer le bloc référencé par la variable, c'est-à-dire qu'elle le rend disponible pour d'autres usages éventuels. Si vous utilisez cette procédure, vous devez de surcroît remettre votre variable à 0 pour plus de sécurité. Sachez que FutureBASIC fournit une fonction préfabriquée pour réaliser cette opération en une seule commande : DEF DISPOSEH non seulement libèrera le bloc et mettra à 0 la variable qui lui est passée en paramètre mais aussi elle s'assurera que le handle qui lui a été transmis n'est pas un handle sur une ressource, car les handles sur des ressources doivent être traités différemment quant à leur libération.

    Bien évidemment, ce type d'exemple, n'est pas ce qu'on commence par montrer aux débutants, mais son propos ici est de dévoiler que l'on dispose d'une certaine liberté lorsqu'on comprend les entités que nos instructions manipulent.

    Le concept de fonction locale a été un apport essentiel de FutureBASIC dans son implémentation du langage BASIC lui-même. Les programmeurs qui connaissent déjà le langage BASIC standard peuvent sans regrets abandonner immédiatement les routines GOSUB/RETURN, ils n'y trouveront que des avantages à commencer par une meilleure structuration de leur code source, mais aussi une plus grande facilité de réutilisation pour d'autres projets des routines qu'ils écrivent, une plus grande efficacité dans l'utilisation des ressources de la machine et en particulier de la mémoire, une aide précieuse pour le débogage (correction des erreurs), le flux du programme étant plus facile à tracer (on conseille généralement d'écrire de petites routines utilisant le moins de variables possible, lorsqu'une fonction devient trop grande il est préférable de la découper en écrivant d'autres fonctions spécialisées auxquelles la routine fera appel), une plus grande simplicité pour d'éventuelles évolutions comme l'ajout de fonctionnalités, une puissance accrue grâce au passage de paramètres, au retour d'une valeur et à la récursivité possible. Voilà encore un nouveau terme !



    [Precédent] [Table des Matières] [Suivant]
  • {Note}
    © 2000 Pix&Mix
    Tous droits réservés

    FutureBASIC est une marque déposée appartenant à Staz Software, Inc et utilisée avec permission.