Procedures et Fonctions Delphi

Cette rubrique traite les sujets suivants :
  • Déclaration de procédures et de fonctions
  • Conventions d'appel
  • Déclarations forward et interface
  • Déclaration de routines externes
  • Surcharge de procédures et de fonctions
  • Déclarations locales et routines imbriquées
A propos des procédures et des fonctions
Les procédures et fonctions, désignées collectivement par le terme routines, sont des blocs d'instructions autonomes qui peuvent être appelés depuis divers endroits d'un programme. Une fonction est une routine qui renvoie une valeur quand elle est exécutée. Une procédure est une routine qui ne renvoie pas de valeur. 
Les appels de fonction peuvent être utilisés comme expression dans les affectations et les opérations car ils renvoient une valeur. Par exemple,

I := SomeFunction(X);
appelle SomeFunction et affecte son résultat à I. Les appels de fonction ne peuvent pas apparaître du côté gauche d'une instruction d'affectation. 
Les appels de procédures -- et, quand la syntaxe étendue est activée, ({$X+}), les appels de fonctions -- peuvent s'utiliser comme des instructions à part entière. Par exemple,

DoSomething;
appelle la routine DoSomething ; si DoSomething est une fonction, la valeur renvoyée est perdue. 
Les procédures et les fonctions peuvent s'appeler elles-mêmes de manière récursive.
Déclaration de procédures et de fonctions
Quand vous déclarez une procédure ou une fonction, vous spécifiez son nom, le nombre et le type de ses paramètres et, dans le cas d'une fonction, le type de la valeur qu'elle renvoie. Cette partie de la déclaration est parfois appelée le prototype ou l'en-tête de la routine. Puis, vous écrivez un bloc de code qui s'exécute à chaque fois que la procédure ou la fonction est appelée. Cette partie est parfois appelée le corps ou le bloc de la routine.
Déclaration de procédures
La déclaration d'une procédure a la forme :

procedure procedureName(parameterList); directives;
  localDeclarations;
begin
  instructions
end;
où procedureName est un identificateur valide, instructions une série d'instructions qui s'exécute quand la procédure est appelée. Les éléments (parameterList)directives; et localDeclarations; sont facultatifs. 
Voici un exemple de déclaration de procédure :

procedure NumString(N: Integer; var S: string);
var
  V : Integer;
begin
  V := Abs(N);
  S := '';
  repeat
    S := Chr(V mod 10 + Ord('0')) + S;
    V := V div 10;
  until V = 0;
  if N < 0 then S := '-' + S;
end;
Etant donné cette déclaration, vous pouvez appeler la procédure NumString de la manière suivante :

NumString(17, MyString);
Cette procédure affecte la valeur "17" à MyString (qui doit être une variable string). 
Dans le bloc d'instructions d'une procédure, vous pouvez utiliser des variables et d'autres identificateurs déclarés dans la partielocalDeclarations de la procédure. Vous pouvez également utiliser les noms de paramètre de la liste de paramètres (comme N et S dans l'exemple précédent). La liste de paramètres définit un ensemble de variables locales, vous ne devez donc pas redéclarer le nom des paramètres dans la section localDeclarations. Vous pouvez, enfin, utiliser tous les identificateurs qui sont dans la portée de la déclaration de la procédure.
Déclarations de fonctions
Une déclaration de fonction est similaire à la déclaration d'une procédure mais elle spécifie le type de la valeur renvoyé. Une déclaration de fonction a la forme suivante :

function functionName(parameterList): returnType; directives;
  localDeclarations;
begin
  instructions
end;
où functionName est un identificateur valide, returnType un identificateur de type, instructions une série d'instructions qui s'exécute quand la procédure est appelée. Les éléments (parameterList)directives; et localDeclarations; sont facultatifs. 
Le bloc instruction d'une fonction respecte les mêmes règles que celles qui s'appliquent aux procédures. A l'intérieur du bloc instruction, vous pouvez utiliser les variables et les autres identificateurs déclarés dans la partie localDeclarations de la fonction, les noms de paramètre de la liste de paramètres et les identificateurs dont la portée est dans la déclaration de la fonction. De plus, le nom de la fonction se comporte comme une variable spéciale contenant la valeur renvoyée par la fonction, à l'image de la variable prédéfinieResult
Si la syntaxe étendue est activée ({$X+}), Result est déclarée implicitement dans chaque fonction. Vous ne devez donc pas la redéclarer. 
Par exemple,

function WF: Integer;
begin
  WF := 17;
end;
définit une fonction constante appelée WF qui n'attend pas de paramètre et renvoie toujours la valeur entière 17. Cette déclaration est équivalent à :

function WF: Integer;
begin
  Result := 17;
end;
Voici un exemple de déclaration de fonction plus compliquée :

function Max(A: array of Real; N: Integer): Real;
var
X: Real;
I: Integer;
begin
  X := A[0];
  for I := 1 to N - 1 do
    if X < A[I] then X := A[I];
  Max := X;
end;
Dans le bloc instruction, vous pouvez affecter plusieurs fois une valeur à Result ou au nom de la fonction ; il faut simplement que le type de la valeur affectée corresponde au type renvoyé déclaré. Quand l'exécution de la fonction s'achève, la dernière valeur affectée àResult ou au nom de la fonction définit la valeur renvoyée par la fonction. Par exemple,

function Power(X: Real; Y: Integer): Real;
var
  I: Integer;
begin
  Result := 1,0;
  I := Y;
  while I > 0 do
   begin
    if Odd(I) then Result := Result * X;
    I := I div 2;
    X := Sqr(X);
   end;
end;
Result et le nom de la fonction représentent toujours la même valeur. Ainsi, la fonction :

function MyFunction: Integer;
begin
  MyFunction := 5;
  Result := Result * 2;
  MyFunction := Result + 1;
end;
renvoie la valeur 11. Cependant, Result n'est pas totalement interchangeable avec le nom de la fonction. Quand le nom de la fonction apparaît à gauche d'une instruction d'affectation, le compilateur suppose qu'il est utilisé (comme Result) pour indiquer la valeur renvoyée. Par contre, quand le nom de la fonction apparaît n'importe où ailleurs dans le bloc instruction, le compilateur l'interprète comme un appel récursif de la fonction. Result, quant à lui, s'utilise comme une variable dans les opérations, les transtypages, les constructeurs d'ensembles, les indices et les appels à d'autres procédures. 
Si la fonction s'achève sans qu'une valeur ne soit affectée à Result ou au nom de la fonction, la valeur renvoyée par la fonction est indéfinie.
Conventions d'appel
Dans la déclaration d'une procédure ou d'une fonction, vous pouvez spécifier une convention d'appel en utilisant l'une des directivesregisterpascalcdeclstdcall et safecall. Par exemple,

function MyFunction(X, Y: Real): Real; cdecl;
Les conventions d'appel déterminent l'ordre dans lequel les paramètres sont transmis à la routine. Elles affectent également la suppression des paramètres de la pile, l'utilisation de registres pour transmettre les paramètres, ainsi que la gestion des erreurs et des exceptions. register est la convention d'appel par défaut.
  • Les conventions register et pascal transmettent les paramètres de gauche à droite ; c'est-à-dire que le paramètre de gauche est évalué et transmis en premier, le paramètre de droite est évalué et transmis en dernier. Les conventions cdeclstdcall et safecalltransmettent les paramètres de droite à gauche.
  • Pour toutes les conventions à l'exception de cdecl, les procédures et fonctions suppriment les paramètres de la pile lors de la sortie. Avec la convention cdecl, c'est à l'appelant de supprimer les paramètres de la pile au retour de l'appel.
  • La convention register utilise jusqu'à trois registres de la CPU pour transmettre des paramètres alors que toutes les autres conventions transmettent tous les paramètres dans la pile.
  • La convention safecall implémente les 'coupe-feu' d'exceptions. Sous Win32, cela implémente la notification d'erreurs COM interprocessus.
Le tableau suivant résume les caractéristiques des conventions d'appel.  
Conventions d'appel  
Directive  
Ordre des paramètres  
Nettoyage  
Transfert des paramètres dans les registres ?  
register  
De gauche à droite  
Routine  
Oui  
pascal  
De gauche à droite  
Routine  
Non  
cdecl  
De droite à gauche  
Appelant  
Non  
stdcall  
De droite à gauche  
Routine  
Non  
safecall  
De droite à gauche  
Routine  
Non  
La convention d'appel par défaut register est la plus efficace car elle évite la création d'un cadre de pile. (Les méthodes d'accès aux propriétés publiées doivent utiliser register.) La convention cdecl est utile pour les appels de fonctions à partir de bibliothèques partagées écrites en C ou en C++, alors que stdcall et safecall sont conservées habituellement pour les appels à du code externe. Sous Win32, les API du système d'exploitation sont stdcall et safecall. Les autres systèmes d'exploitation utilisent généralementcdecl. Notez que stdcall est plus efficace que cdecl
La convention safecall doit être utilisée pour déclarer les méthodes des interfaces doubles. La convention pascal est conservée dans un souci de compatibilité ascendante.  
Les directives nearfar et export désignent des conventions d'appels utilisées dans la programmation Windows 16 bits. Elles sont sans effet dans les applications Win32 et sont conservées uniquement dans un souci de compatibilité ascendante.
Déclarations forward et interface
Dans une déclaration de procédure ou de fonction, la directive forward se substitue au bloc, y compris à la déclaration des variables locales et des instructions. Par exemple,

function Calculate(X, Y: Integer): Real; forward;
déclare une fonction appelée Calculate. Quelque part après la déclaration forward, la routine doit être redéclarée dans une déclaration de définition qui contient un bloc. La déclaration de définition de la fonction Calculate peut être :

function Calculate;
  ... { déclarations }
begin
  ... { bloc instruction}
end;
Généralement, une déclaration de définition ne répète pas la liste des paramètres ou le type renvoyé par la routine. Mais si vous les répétez, ils doivent correspondre exactement à ceux faits dans la déclaration forward (à cette différence que les paramètres par défaut peuvent être omis). Si la déclaration forward spécifie une procédure ou une fonction surchargée, la déclaration de définition doit alors répéter la liste des paramètres. 
Une déclaration forward et sa déclaration de définition doivent apparaître dans la même section de déclaration de type. C'est-à-dire que vous ne pouvez pas ajouter une nouvelle section (comme une section var ou const) entre la déclaration forward et la déclaration de définition. La déclaration de définition peut être une déclaration external ou assembler, mais pas une autre déclaration forward
Le rôle d'une déclaration forward est d'étendre en avant dans le code la portée d'un identificateur de routine. Cela permet à d'autres procédures et fonctions d'appeler la routine déclarée forward avant qu'elle ne soit effectivement définie. En dehors du fait que cela permet d'organiser le code de manière plus souple, les déclarations forward sont parfois indispensables dans le cas de récursions mutuelles. 
La directive forward n'a pas d'effet dans la section interface d'une unité. Les en-têtes de procédures et de fonctions de la sectioninterface se comportent comme des déclarations forward dont la déclaration de définition doit se trouver dans la sectionimplementation. Une routine déclarée dans la section interface est utilisable partout ailleurs dans l'unité et dans toutes les unités et programmes utilisant l'unité où elle est déclarée.
Déclarations externes
La directive external qui remplace le bloc dans une déclaration de procédure ou de fonction permet d'appeler des routines compilées séparément de votre programme. Les routines externes peuvent être issues de fichiers objet ou de bibliothèques à chargement dynamique. 
Quand vous importez une fonction C qui prend un nombre variable de paramètres, utilisez la directive varargs. Par exemple,

function printf(Format: PChar): Integer; cdecl; varargs;
La directive varargs fonctionne uniquement avec des routines externes et uniquement avec la convention d'appel cdecl.
Liaison de fichiers objet
Pour appeler des routines d'un fichier objet compilé séparément, commencez par lier le fichier objet à votre application en utilisant la directive de compilation $L (ou $LINK). Par exemple,

{$L BLOCK.OBJ}
lie BLOCK.OBJ au programme ou à l'unité dans laquelle cette directive est placée. Ensuite, déclarez les fonctions et procédures que vous voulez appeler :

procedure MoveWord(var Source, Dest; Count: Integer); external;
procedure FillWord(var Dest; Data: Integer; Count: Integer); external;
Vous pouvez ensuite appeler les routines MoveWord et FillWord depuis BLOCK.OBJ
Sur la plate-forme Win32, de telles déclarations sont fréquemment utilisées pour accéder à des routines externes écrites en assembleur. Vous pouvez aussi placer directement des routines en assembleur dans votre code source Delphi.
Importation des fonctions de bibliothèques
Pour importer des routines d'une bibliothèque à chargement dynamique (.DLL), attachez une directive de la forme : 
externalstringConstant
à la fin de l'en-tête de la fonction ou de la procédure, où stringConstant est le nom du fichier bibliothèque placé entre apostrophes. Par exemple, sous Win32 :

function SomeFunction(S: string): string; external 'strlib.dll';
importe de strlib.dll une fonction appelée SomeFunction
Vous pouvez importer une routine sous un nom différent de celui qu'elle a dans la bibliothèque. Si vous le faites, spécifiez le nom original dans la directive external : 
externalstringConstant1namestringConstant2; 
où la première stringConstant spécifie le nom du fichier bibliothèque et la seconde stringConstant est le nom original de la routine. 
La déclaration suivante importe une fonction depuis user32.dll (partie de l'API Win32).

function MessageBox(HWnd: Integer; Text, Caption: PChar; Flags: Integer): Integer; stdcall; external 'user32.dll' name 'MessageBoxA';
Le nom d'origine de la fonction est MessageBoxA, mais elle est importée sous le nom MessageBox
A la place du nom, vous pouvez utiliser un numéro pour identifier la routine à importer : 
externalstringConstantindexintegerConstant; 
où integerConstant et l'indice de la routine dans la table d'exportation. 
Dans la déclaration d'importation, assurez-vous de respecter exactement l'orthographe et la casse du nom de la routine. Par contre, une fois la routine importée, il n'y a plus de différences majuscules/minuscules.
Surcharge de procédures et de fonctions
Il est possible de redéclarer plusieurs fois une routine dans la même portée sous le même nom. C'est ce que l'on appelle la surcharge. Les routines surchargées doivent être déclarées avec la directive overload et doivent utiliser une liste de paramètres différente. Soit, par exemple, les déclarations :

function Divide(X, Y: Real): Real; overload;
begin
  Result := X/Y;
end

function Divide(X, Y: Integer): Integer; overload;
begin
  Result := X div Y;
end;
Ces déclarations créent deux fonctions appelées toutes les deux Divide qui attendent des paramètres de types différents. Quand vous appelez Divide, le compilateur détermine la fonction à utiliser en examinant les paramètres effectivement transmis dans l'appel. Ainsi,Divide(6.0, 3.0) appelle la première fonction Divide car ses arguments sont des valeurs réelles. 
Vous pouvez transmettre à une routine surchargée des paramètres qui ne sont pas du même type que ceux d'une des déclarations de la routine, mais qui sont compatibles au niveau de l'affectation avec des paramètres d'une ou de plusieurs déclarations. Cela arrive le plus souvent quand une routine est surchargée avec différents types entiers ou différents types réels -- par exemple,

procedure Store(X: Longint); overload;
procedure Store(X: Shortint); overload;
Dans ce cas, quand c'est possible de le faire sans ambiguïté, le compilateur appelle la routine dont les paramètres sont du type de la plus petite étendue qui convienne aux paramètres réels de l'appel. N'oubliez pas que les expressions de constantes réelles sont toujours de type Extended
Les routines surchargées doivent pouvoir se distinguer par le nombre ou le type de leurs paramètres. Ainsi, la paire de déclarations suivante déclenche une erreur de compilation :

function Cap(S: string): string; overload;
  ...
procedure Cap(var Str: string); overload;
  ...
Alors que les déclarations :

function Func(X: Real; Y: Integer): Real; overload;
  ...
function Func(X: Integer; Y: Real): Real; overload;
  ...
sont correctes. 
Quand une routine surchargée est déclarée dans une déclaration forward ou interface, la déclaration de définition doit obligatoirement répéter la liste des paramètres de la routine. 
Le compilateur peut distinguer les fonctions surchargées qui contiennent les paramètres AnsiString/PChar et WideString/WideCharà la même position de paramètre. Les constantes de chaînes et les littéraux transmis dans cette situation de surcharge sont traduits en chaîne ou type de caractère natif, c'est-à-dire AnsiString/PChar.

procedure test(const S: String);  overload;
procedure test(const W: WideString); overload;
var
  a: string;
  b: widestring;
begin
  a := 'a';
  b := 'b';
  test(a);    // appelle la version String
  test(b);    // appelle la version WideString
  test('abc');    // appelle la version String
  test(WideString('abc'));   // appelle la version widestring
end;
Les variants peuvent également être utilisés en tant que paramètres dans des déclarations de fonctions surchargées. Le variant est considéré comme étant plus général que tout type simple. La préférence est toujours donnée aux correspondances de type exact sur les correspondances de variant. Si un variant est transmis dans cette situation de surcharge, et si une surcharge avec variant existe à cette position de paramètre, elle est considérée comme étant une correspondance exacte pour le type Variant. 
Cela peut provoquer des effets secondaires mineurs avec les types flottants. La correspondance des types flottants s'effectue par la taille. S'il n'y a pas de correspondance exacte pour la variable flottante transmise à l'appel surchargé, mais qu'un paramètre variant est disponible, le variant est pris par rapport à tout type flottant plus petit. 
Par exemple :

procedure foo(i: integer); overload;
procedure foo(d: double); overload;
procedure foo(v: variant); overload;
var
  v : variant;
begin
  foo(1);       // version entier
  foo(v);       // version variant
  foo(1.2);     // version variant (littéraux flottants -> précision étendue)
end;
Cet exemple appelle la version variant de foo, et pas la version double, car la constante 1.2 est implicitement un type extended, et ce type n'est pas une correspondance exacte pour double. Il n'est pas non plus une correspondance exacte pour Variant, mais Variantest considéré comme un type plus général (alors que double est un type plus petit que extended).

foo(Double(1.2));
Ce transtypage ne fonctionne pas. Vous devrez utiliser des constantes typées à la place.

const  d: double = 1.2;
begin
   foo(d);
end;
Le code ci-dessus fonctionne correctement, et il appelle la version double.

const  s: single = 1.2;
begin
  foo(s);
end;
Le code ci-dessus appelle également la version double de foo. Single convient mieux à double qu'à variant
Lors de la déclaration d'un ensemble de routines surchargées, la meilleure façon d'éviter le passage de flottant à variant consiste à déclarer une version de votre fonction surchargée pour chaque type flottant (SingleDoubleExtended) avec la version variant
Si vous utilisez des paramètres par défaut dans des routines surchargées, faites attention à ne pas introduire de signatures de paramètres ambiguës. 
Vous pouvez limiter les effets potentiels de la surcharge en qualifiant le nom d'une routine lors de son appel. Par exemple,Unit1.MyProcedure(X, Y) n'appelle que les routines déclarées dans Unit1; ; si aucune routine de Unit1 ne correspond au nom et à la liste des paramètres, il y a une erreur de compilation.
Déclarations locales
Le corps d'une fonction ou d'une procédure commence souvent par la déclaration de variables locales utilisées dans le bloc instruction de la routine. Ces déclarations peuvent également contenir des constantes, des types ou d'autres routines. La portée d'un identificateur local est limitée à celle de la routine dans laquelle il est déclaré.
Routines imbriquées
Les fonctions et procédures contiennent parfois d'autres fonctions ou procédures dans la section des déclarations locales de leur bloc. Par exemple le code suivant déclare une procédure DoSomething qui contient une procédure imbriquée :

procedure DoSomething(S: string);
var
  X, Y: Integer;
            
procedure NestedProc(S: string);
begin
  ...
end;
 
begin
  ...
  NestedProc(S);
  ...
end;
La portée d'une routine imbriquée est limitée à la fonction ou la procédure dans laquelle elle est déclarée. Dans l'exemple précédent,NestedProc ne peut être appelée que dans DoSomething
Pour des exemples réels de procédures imbriquées, examinez la procédure DateTimeToString, la fonction ScanDate et d'autres routines de l'unité SysUtils.

Aucun commentaire: