[delphi] compatibilité et directives

[delphi] compatibilité et directives

Description

C'est un petit tutoriel expliquant globalement comment accroître la compatibilité de vos codes sources en exploitant les caractéristiques de quelques directives de compilation.
Une version complète de ce tutorial peut-être trouvée au lien suivant:
http://codes-sources.commentcamarche.net/source/27845-tutorial-compatibilite-et-directives

Directives générales des applications console

On va développer une application console.
On travaille uniquement dans un fichier DPR. Supprimez donc les fichiers Unit1.pas et Unit1.dfm dans le répertoire du projet.

Il faut activer le mode CONSOLE, terme opposé à GUI. On utilise alors la directive $APPTYPE, déclarée juste en dessous de la clause USES, et valant GUI par défaut. On pourrait utiliser les options du projet, mais dans le cas, l'option est sauvegardée dans un fichier DOF ou CFG, et donc non directement visible.

Sous réserve de ne pas utiliser l'unité Forms (et tout autre unité déclarant Forms) l'application est terriblement allégée. Elle l'est d'autant plus que la version de Delphi est basse. L'interface est entièrement affichée dans une fenêtre Ms-Dos toute noire, et non dans une fenêtre de Windows (GUI) comme il est courant de voir.

La directive $APPTYPE permet également d'activer les fonctions Write, WriteLn, Read et ReadLn (dans le cadre d'une application console). En son absence, Delphi aurait généré une erreur. Ces quatre fonctions permettent d'afficher des lignes de texte dans la console.

program compatib_delphi;

uses SysUtils;
{$APPTYPE Console}

begin
end.

Si on veut encore alléger l'application, il est possible d'empêcher le compilateur d'incorporer des ressources supplémentaires, souvent inutiles bien qu'elles ne contiennent la plupart du temps que des icônes et des informations de version. Ces informations sont gérées dans les options du projet.

Pour cela, on désactive la directive $R. Elle peut se placer en dessous de la clause USES, ou du mot réservé IMPLEMENTATION. Ici, ce mot réservé n'apparaît pas dans le programme. En effet, on travaille dans un DPR, et non dans un PAS. C'est un héritage du Pascal qui le veut. En effet, un DPR orphelin est susceptible d'être compilable avec Turbo Pascal, pas les fichiers PAS de Delphi.

program compatib_delphi;

uses SysUtils;
{$R-}

begin
end.

Un fichier RES peut cependant rester présent dans le répertoire du projet. En effet, Delphi le garde au cas où vous voudriez le réactiver. Dans ce cas, il faut noter à la place du moins, le nom du fichier.
Exemple: {$R toto.res}

Gérer les versions de Delphi

Pour "créer" des directives, on part toujours de celles déjà proposées par Delphi. Mais on ne fait souvent que les renommer pour mieux les retenir. Par exemple, on passe de VER160 à _D8, et pourtant, c'est la même chose. De plus, on définit _D4_UP à partir de ces dernières.

Détectons les versions de Delphi en définissant des clés :

program compatib_delphi;

uses SysUtils;

{$IFDEF VER160} {$DEFINE _D8} {$ENDIF} //!! pas sûr !!//
{$IFDEF VER150} {$DEFINE _D7} {$ENDIF}
{$IFDEF VER140} {$DEFINE _D6} {$ENDIF}
{$IFDEF VER130} {$DEFINE _D5} {$ENDIF}
{$IFDEF VER125} {$DEFINE _D4} {$ENDIF}
{$IFDEF VER120} {$DEFINE _D4} {$ENDIF}
{$IFDEF VER110} {$DEFINE _D3} {$ENDIF}
{$IFDEF VER100} {$DEFINE _D3} {$ENDIF}
{$IFDEF VER93}  {$DEFINE _D2} {$ENDIF}
{$IFDEF VER90}  {$DEFINE _D2} {$ENDIF}
{$IFDEF VER80}  {$DEFINE _D1} {$ENDIF}

{$IFDEF _D4} {$DEFINE _D4_UP} {$ENDIF}
{$IFDEF _D5} {$DEFINE _D4_UP} {$ENDIF}
{$IFDEF _D6} {$DEFINE _D4_UP} {$ENDIF}
{$IFDEF _D7} {$DEFINE _D4_UP} {$ENDIF}
{$IFDEF _D8} {$DEFINE _D4_UP} {$ENDIF}

begin
end.

La syntaxe se passe de commentaires.

L'utilisation de ces déclarations se fera ci-dessous.

Gérer les conseils

Cette gestion se fait via la directive $HINTS. Par défaut, elle est ON, terme opposé à OFF.

Dans l'exemple suivant, il faut désactiver $HINTS. En effet R:=0; est signalé incorrect puisque quelques lignes plus loin, R est initialisée à nouveau. Delphi prend en effet en compte une ligne de code, considérée à priori par le développeur comme étant autant susceptible d'être codée que les autres. Tout dépend de la version de Delphi de l'utilisateur. Il faut tout de même, initialiser R à 0, valeur d'erreur par défaut au cas où aucune version ne serait trouvée. Cela génère un conseil gênant qui est ici éliminé par la directive. En théorie, R:=0; est inutile, mais on n'en sait rien.

Remarquez comment $HINTS est disposée : à l'extérieur de la procédure sous forme de bornes.

{$HINTS OFF}
function GetDelphiVersion:byte;
var r : byte;
begin
  R:=0;
  {$IFDEF _D1}R:=1;{$ENDIF}
  {$IFDEF _D2}R:=2;{$ENDIF}
  {$IFDEF _D3}R:=3;{$ENDIF}
  {$IFDEF _D4}R:=4;{$ENDIF}
  {$IFDEF _D5}R:=5;{$ENDIF}
  {$IFDEF _D6}R:=6;{$ENDIF}
  {$IFDEF _D7}R:=7;{$ENDIF}
  {$IFDEF _D8}R:=8;{$ENDIF}
  GetDelphiVersion:=R;
end;
{$HINTS ON}

Delphi interprétera la ligne qui correspondra à sa version, et non toutes. Si vous avez Delphi 3, alors les autres lignes peuvent contenir tout et n'importe quoi. Mais une autre version n'interprétera pas la même ligne. D'où l'importance de ne pas commettre d'erreurs, qui ne pourront pas être détectées, sauf si le développeur zappe entre les versions.

Gérer les avertissements

De même, la directive $WARNINGS n'est fonctionnelle qu'à l'extérieur d'une procédure ou d'une fonction. Ici, l'avertissement vient de l'absence d'initialisation de R, qui est la valeur résultat de notre fonction. En conclusion, cette fonction ne renvoie que des résultats absurdes. Et c'est tout à fait compréhensible

{$WARNINGS OFF}
function AWarningErrorFunction:byte;
var R : byte;
begin
  AWarningErrorFunction:=R;
end;
{$WARNINGS ON}

Ceci dit, la variable R reste toujours non initialisée. La désactivation de $WARNINGS ne résout en rien le problème. Ici, c'est juste pour illustrer, mais $WARNINGS OFF permet en général d'empêcher l'affichage d'un message d'erreur uniquement lorsqu'il n'y a plus rien à faire, bien que la fonction soit tout à fait cohérente et fonctionnelle. Il existe des cas pathologiques qu'il faut donc gérer.

Optimiser un INTEGER

Type
{T}SuperInteger = {$IFNDEF _D4_UP} integer {$ELSE} INT64 {$ENDIF};

On voit apparaître la directive $IFNDEF qui correspond à "si qqch est non défini alors...". Au début, on avait seulement utilisé la directive $IFDEF (correspondant à "si qqch est défini alors..."). Ici, on peut en plus utiliser la directive $ELSE au cas ou une directive aurait échoué.

Cette déclaration de SuperInt permet d'obtenir un entier de taille maximale. Avant Delphi 4, l'entier le plus grand possible était INTEGER et valait 232, soit un intervalle de 4 milliards d'entiers. Mais depuis Delphi 4, il existe un entier bien plus grand valant 264, c'est à dire un intervalle de 18 milliards de milliards d'entiers, que Delphi 3 ne supporte absolument pas. Il faudrait alors passer par le type COMP (computational), mais il est stocké au format décimal, ce qui crée des erreurs avec StrToInt par exemple. Un décimal n'est pas un entier. On ne peut donc pas appliquer les mêmes fonctions sur eux.

En déclarant un SuperInt, on optimise un entier de telle manière que ce soit le plus grand possible dans la version de Delphi utilisée par l'utilisateur. En recevant un code source, il peut directement compiler son projet sans aucune erreur de compatibilité puisqu'il n'a pas à remplacer manuellement les INT64 en INTEGER.

Ceci dit, si un projet avait préconisé l'usage de INT64, le passage en INTEGER (plus petit, donc) peut causer des soucis. Prenons un exemple. Si le INT64 stocke la taille d'un disque dur (chiffre de 11 milliards non stockable dans 2 milliards) alors une version antérieure de Delphi, fera une réduction des 11 milliards d'octets (environ un disque dur de 10Go) à 2 milliards. Ceci n'est vraiment plus l'information initiale. Il faut donc être prudent, ou ne pas utiliser INT64 trop abusivement.

Dans le type, un T est mis en commentaire. C'est uniquement pour ne pas choquer. En effet, l'habitude dans les déclarations de type est de noter tel que "TBiduleTruc", "TMachin"... Mais vous ne déclarez jamais TBoolean, TByte, TInteger ?? Et pourtant ce sont des types ! Donc, par analogie, j'enlève le T.

Optimiser une classe

La classe suivante est un type. Elle porte donc un T. Définissons la comme non dérivée afin qu'elle soit la plus basique possible. Elle ne peut donc servir que comme classe primitive pour une sous-classe.

type
  TMyClass = class
  private
    FProperty
{#1}{$IFDEF _D4_UP}, FPropertyReservee{$ENDIF} : byte;
  published
    property PourTous:byte read FProperty write FProperty;
    {$IFDEF _D4_UP}property ReserveAuxD4Plus:byte read FPropertyReservee write FPropertyReservee;{$ENDIF}
  end;

Dans cette classe non initialisée (pas de CONSTRUCTOR établi), une propriété sera publiée dans toutes les versions de Delphi. En revanche, l'autre sera réservée aux possesseurs d'une version 4+ de Delphi.

Supposons qu'il n'y ait pas de directives au marqueur #1. Ceux qui ont Delphi 3 et moins verraient un conseil disant que FPropertyReservee n'est pas utilisée. Donc, on est obligé de diriger la ligne marquée, qui est une conséquence de la restriction des versions au niveau des propriétés publiées.

Remarquons la place de la virgule dans la ligne marquée !!

L'utilité ici est de ne pas déclarer inutilement des propriétés. Par exemple ANCHORS n'existe pas sous Delphi 3 bien qu'elle le soit par exemple sous Delphi 5. Cette propriété (activée par dérivation de la classe) sera donc encadrée par ces balises dans le cadre d'un développement d'un composant.

Code source final

On vient donc d'écrire le programme suivant. Il n'a pas de sens en lui-même, mais comporte plein de choses intéressantes.

program compatib_delphi;

uses SysUtils;

{$R-}
{$APPTYPE Console}

{$IFDEF VER160} {$DEFINE _D8} {$ENDIF}
{$IFDEF VER150} {$DEFINE _D7} {$ENDIF}
{$IFDEF VER140} {$DEFINE _D6} {$ENDIF}
{$IFDEF VER130} {$DEFINE _D5} {$ENDIF}
{$IFDEF VER125} {$DEFINE _D4} {$ENDIF}
{$IFDEF VER120} {$DEFINE _D4} {$ENDIF}
{$IFDEF VER110} {$DEFINE _D3} {$ENDIF}
{$IFDEF VER100} {$DEFINE _D3} {$ENDIF}
{$IFDEF VER93}  {$DEFINE _D2} {$ENDIF}
{$IFDEF VER90}  {$DEFINE _D2} {$ENDIF}
{$IFDEF VER80}  {$DEFINE _D1} {$ENDIF}

{$IFDEF _D4} {$DEFINE _D4_UP} {$ENDIF}
{$IFDEF _D5} {$DEFINE _D4_UP} {$ENDIF}
{$IFDEF _D6} {$DEFINE _D4_UP} {$ENDIF}
{$IFDEF _D7} {$DEFINE _D4_UP} {$ENDIF}
{$IFDEF _D8} {$DEFINE _D4_UP} {$ENDIF}
 

{$HINTS OFF}
function GetDelphiVersion:byte;
var r : byte;
begin
  R:=0;
  {$IFDEF _D1}R:=1;{$ENDIF}
  {$IFDEF _D2}R:=2;{$ENDIF}
  {$IFDEF _D3}R:=3;{$ENDIF}
  {$IFDEF _D4}R:=4;{$ENDIF}
  {$IFDEF _D5}R:=5;{$ENDIF}
  {$IFDEF _D6}R:=6;{$ENDIF}
  {$IFDEF _D7}R:=7;{$ENDIF}
  {$IFDEF _D8}R:=8;{$ENDIF}
  GetDelphiVersion:=R;
end;
{$HINTS ON}

{$WARNINGS OFF}
function AWarningErrorFunction:byte;
var R : byte;
begin
  AWarningErrorFunction:=R;
end;
{$WARNINGS ON}

type
  SuperInteger = {$IFNDEF _D4_UP} integer {$ELSE} INT64 {$ENDIF};
  
  TMyClass = class
  private
    FProperty
    {$IFDEF _D4_UP}, FPropertyReservee{$ENDIF} : byte;
  published
    property PourTous:byte read FProperty write FProperty;
    {$IFDEF _D4_UP}property ReserveAuxD4Plus:byte read FPropertyReservee write FPropertyReservee;{$ENDIF}
  end;

begin
  WriteLn('Vous utilisez Delphi '+IntToStr(GetDelphiVersion));
  ReadLn;
end.

Remerciements

Clin d'oeil à Japee pour une réponse qu'il m'a fourni, même si ce fut bien peu de chose.
DelphiProg et JulioDelphi pour leur réaction sur le code source.

Tanguy ALTERT,
http://altert.family.free.fr/

Ce document intitulé « [delphi] compatibilité et directives » issu de CodeS SourceS (codes-sources.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.
Rejoignez-nous