Procédures et fonctions (Delphi)

De RAD Studio
Aller à : navigation, rechercher

Remonter à Procédures et fonctions - Index


  • 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 le 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, sa valeur de retour 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éclarations de procédures

Une déclaration de procédure a la forme :

 procedure procedureName(parameterList); directives;
   localDeclarations;
 begin
   statements
 end;

procedureName est un identificateur valide, statements 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);

Cet appel de 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 partie localDeclarations de la procédure. Vous pouvez aussi utiliser les noms de paramètres 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 de procédure, mais elle spécifie un type de retour et une valeur de retour. Les déclarations de fonctions ont la forme suivante :

 function functionName(parameterList): returnType; directives;
   localDeclarations;
 begin
   statements
 end;

functionName est un identificateur valide, returnType est un identificateur de type, statements est une série d'instructions qui s'exécute quand la fonction est appelée. Les éléments (parameterList), directives; et localDeclarations; sont facultatifs.

Le bloc d'instructions d'une fonction respecte les mêmes règles que celles qui s'appliquent aux procédures. A l'intérieur du bloc d'instructions, vous pouvez utiliser les variables et autres identificateurs déclarés dans la partie localDeclarations de la fonction, les noms de paramètres 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 de retour de la fonction, à l'image de la variable prédéfinie Result.

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 équivalente à :

 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;

Vous pouvez affecter une valeur à Result ou au nom de la fonction de façon répétitive au sein d'un bloc d'instructions, tant que vous n'affectez que des valeurs qui correspondent au type de retour 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 devient la valeur de retour de 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 de retour. Par contre, quand le nom de la fonction apparaît n'importe où ailleurs dans le bloc d'instructions, le compilateur l'interprète comme un appel récursif à la fonction. Result, quant à lui, s'utilise comme une variable dans les opérations, les transtypages, les constructeurs d'ensembles, les index et les appels à d'autres routines.

Si la fonction s'achève sans qu'une valeur ne soit affectée à Result ou au nom de la fonction, la valeur de retour de 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 directives register, pascal, cdecl, stdcall, safecall et winapi. 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 aussi 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.

  • Pour les conventions register et pascal, l'ordre d'évaluation n'est pas défini.
  • Les conventions cdecl, stdcall et safecall transmettent 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, l'appelant retire les paramètres de la pile au retour de l'appel.
  • La convention register utilise jusqu'à trois registres CPU pour transmettre des paramètres, alors que toutes les autres conventions transmettent tous les paramètres sur la pile.
  • La convention safecall implémente les 'coupe-feu' d'exceptions. Sur Win32, cela implémente la notification d'erreurs COM interprocessus.
  • winapi n'est pas réellement une convention d'appel. winapi se définit en utilisant la convention d'appel de la plate-forme par défaut. Par exemple, sur Win32, winapi est identique à stdcall.

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 

Non défini 

Routine 

Oui

pascal

Non défini

Routine

Non

cdecl

De droite à gauche

Appelant

Non

stdcall

De droite à gauche

Routine

Non

safecall

De droite à gauche

Routine

Non


La convention par défaut register est la plus efficace car elle évite habituellement 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 recommandées généralement pour les appels à du code externe. Sur Win32, les APIs du système d'exploitation sont stdcall et safecall. Les autres systèmes d'exploitation utilisent généralement cdecl. 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é descendante.

Les directives near, far et export se réfèrent aux conventions d'appels en programmation Windows 16 bits. Elles sont sans effet dans Win32 et sont conservées uniquement dans un souci de compatibilité descendante.

Déclarations forward et interface

Dans une déclaration de procédure ou de fonction, la directive forward remplace le bloc, y compris les instructions et les déclarations des variables locales. 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 inclut un bloc. La déclaration de définition de Calculate peut être :

 function Calculate;
   ... { declarations }
 begin
   ... { statement block }
 end;

Généralement, une déclaration de définition ne répète pas la liste des paramètres ou le type de retour de la routine. Mais s'ils sont répétés, ils doivent correspondre exactement à ceux de 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 (telle qu'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 but d'une déclaration forward est d'étendre la portée d'un identificateur de procédure ou de fonction à un point antérieur du code source. 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 section interface se comportent comme des déclarations forward et doivent avoir des déclarations de définition dans la section implementation. Une routine déclarée dans la section interface est disponible partout ailleurs dans l'unité et dans toute unité et tout programme qui utilise 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 seulement avec des routines externes et seulement avec la convention d'appel cdecl.

Liaison aux fichiers objet

Pour appeler des routines depuis 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 lequel elle apparaît. 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 de fonctions depuis des bibliothèques

Pour importer des routines depuis une bibliothèque à chargement dynamique (.DLL), attachez une directive de la forme :

external stringConstant;

à 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, sur Windows 32 bits

function SomeFunction(S: string): string; external 'strlib.dll';

importe une fonction appelée SomeFunction à partir de strlib.dll.

Utilisation des lieurs internes et externes

Delphi a deux interprétations du terme "externe" selon que le compilateur utilise un lieur externe ou non


1. Les plates-formes prises en charge par Delphi peuvent être divisées dans les deux groupes suivants :

  • Lorsque le compilateur utilise son propre lieur interne.
  • Lorsque le compilateur utilise un lieur externe.

Utilisation d'un lieur interne 

WIN32, WIN64, OSX, Simulateur IOS 

Utilisation d'un lieur externe  

Périphérique iOS, Android, Linux, macOS64 

2. Sur les plates-formes où Delphi utilise un lieur interne, external <stringconstant> indique que la fonction/procédure réside dans un fichier DLL, dylib ou Shared Object.

Sur ces plates-formes, Delphi comprend que le symbole est importé depuis .dll/.dylib/.so. Aucune validation n'est effectuée au moment de la liaison. Cet exemple génère une référence de chemin relative vers une image. Si le symbole n'est pas dans cette bibliothèque, vous en serez averti à l'exécution.

3. Sur des plates-formes où Delphi utilise un lieur externe, par exemple : lorsque la cible est la plate-forme iOSDevice32, l'identificateur spécifié via external <stringconstant> est transmis au lieur externe.

Le compilateur Delphi transmet <name> à ld.exe. S'il ne trouve pas la bibliothèque, le message d'erreur suivant apparaît : Error: E2597 ld: file not found: <name>.

Importation de fonctions depuis des fichiers objet (lieur externe uniquement)

Lorsque vous utilisez un lieur externe, vous pouvez éliminer l'utilisation de la directive de compilation $L (ou $LINK) en spécifiant le fichier objet avec la directive externe. Par exemple :

procedure FunctionName; cdecl; external object 'ObjectFile.o' name '_FunctionName';

Importation de fonctions depuis des frameworks

Vous pouvez importer des routines depuis un framework externe. Par exemple :

Function foo: Integer; external framework 'framework name>'

Cela s'applique seulement aux cibles iOS32, iOS64 et macOS64.

Importation d'une routine sous un nom différent

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 :

external stringConstant1 name stringConstant2;

stringConstant1 spécifie le nom du fichier bibliothèque et stringConstant2 est le nom original de la routine.

La déclaration suivante importe une fonction depuis user32.dll (partie de l'API Windows) :

function MessageBox(HWnd: Integer; Text, Caption: PChar; Flags: Integer): Integer; stdcall; external 'user32.dll' name 'MessageBoxA';

Le nom original de la fonction est MessageBoxA, mais elle est importée sous le nom MessageBox.

Dans votre déclaration d'importationn, assurez-vous de respecter exactement l'orthographe et la casse du nom de la routine. Par la suite, quand vous appelez la routine importée, le nom est insensible à la casse.

Importation d'une routine par index

A la place du nom, vous pouvez utiliser un numéro pour identifier la routine à importer :

external stringConstant index integerConstant;

integerConstant est l'index de la routine dans la table d'exportation.

Retard chargement de la bibliothèque

Pour différer le chargement de la bibliothèque qui contient la fonction au moment où la fonction est réellement nécessaire, ajoutez la directive delayed à la fonction importée :

function ExternalMethod(const SomeString: PChar): Integer; stdcall; external 'cstyle.dll' delayed;

delayed garantit que la bibliothèque qui contient la fonction importée n'est pas chargée au démarrage de l'application, mais plutôt au premier appel de la fonction. Pour de plus amples informations sur cette rubrique, voir la rubrique Bibliothèques et packages - Chargement différé.

Spécification des dépendances de la bibliothèque

Si la bibliothèque qui contient la routin cible dépend d'autres bibliothèques, utilisez la directive dependency pour spécifier ces dépendances.

Pour utiliser la directive dependency, ajoutez le mot-clé dependency suivi par une liste de chaînes séparées par des virgules. Chaque chaîne doit contenir le nom d'une bibliothèque qui est une dépendance de la bibliothèque esterne cible :

external <library> dépendance <dependency1>, <dependency2>, …

La déclaration suivantes indique que libmidas.a dépend de la bibliothèque C++ standard :

function DllGetDataSnapClassObject(const [REF] CLSID, [REF] IID: TGUID; var Obj): HResult; cdecl; external 'libmidas.a' dependency 'stdc++';

Surcharge de procédures et de fonctions

Vous pouvez dé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 des listes de paramètres différentes. Par exemple, soit 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 prennent des paramètres de types différents. Quand vous appelez Divide, le compilateur détermine la fonction à invoquer 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 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 invoque la routine dont les paramètres sont du type du plus petit intervalle 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 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/PAnsiChar, UnicodeString/PChar et WideString/PWideChar à 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 UnicodeString/PChar.

 procedure test(const A: AnsiString); overload;
 procedure test(const W: WideString); overload;
 procedure test(const U: UnicodeString); overload;
 procedure test(const PW: PWideChar); overload;
 var
   a: AnsiString;
   b: WideString;
   c: UnicodeString;
   d: PWideChar;
   e: string;
 begin
   a := 'a';
   b := 'b';
   c := 'c';
   d := 'd';
   e := 'e';
   test(a);    // calls AnsiString version
   test(b);    // calls WideString version
   test(c);    // calls UnicodeString version
   test(d);    // calls PWideChar version
   test(e);    // calls UnicodeString version
   test('abc');    // calls UnicodeString version
   test(AnsiString ('abc'));    // calls AnsiString version
   test(WideString('abc'));    // calls WideString version
   test(PWideChar('PWideChar'));    // calls PWideChar version
 end;

Les variants peuvent aussi ê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 qui prend un 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. Les types flottants correspondent 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);       // integer version
   foo(v);       // variant version
   foo(1.2);     // variant version (float literals -> extended precision)
 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. Extended n'est pas non plus une correspondance exacte pour Variant, mais Variant est 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 devriez 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 aussi 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 (Single, Double, Extended) 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) peut n'appeler que les routines déclarées dans Unit1; si aucune routine de Unit1 ne correspond au nom et à la liste de paramètres de l'appel, une erreur de compilation apparaît.

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 d'instructions de la routine. Ces déclarations peuvent aussi contenir des constantes, des types ou d'autres routines. La portée d'un identificateur local est limitée à la routine où 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 leurs blocs. Par exemple, la déclaration suivante d'une procédure appelée DoSomething 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 notre exemple, NestedProc peut seulement être appelée dans DoSomething.

Pour des exemples réels de routines imbriquées, examinez la procédure DateTimeToString, la fonction ScanDate et d'autres routines de l'unité SysUtils.

Voir aussi