1. Rappel

Vous trouverez dans le tutoriel suivant le rappel des évolutions du langage sous Delphi 2005 - Win32.
Pour un aperçu sur certaines évolutions du langage identique sur les plateformes Win32 et .NET, consultez le tutoriel sur le langage Delphi .NET.

2. Alias de type

Delphi permettait déjà de créer des alias de type tels que

 
Sélectionnez

type TMaString = string;

autorisant la déclaration de variable suivante :

 
Sélectionnez

 var 
  Chaine1: TMaString;
  Chaine2: string;

Ici Chaine1 et Chaine2 sont du même type.
Si on souhaite distinguer le type exact de ces 2 variables cela n'est pas possible car TMaString est un nom de type différent de string mais si on veut créer un autre type, vu comme tel par le compilateur, il faut utiliser la syntaxe suivante :

 
Sélectionnez

type TMaString = type string;

Dans ce cas le passage en paramètre par référence d'une variable de ce type provoquera une erreur de compilation :

 
Sélectionnez

type
  TMaString1 = string;
  TMaString2 = type string;

 var
  Chaine: string;
  Chaine1: TMaString1;
  Chaine2: TMaString2;

  procedure Affiche(S:TMaString2);
  begin
  end;

  procedure Affiche_Var(Var S:TMaString2);
  begin
  end;

begin
 Chaine:='Type Chaine classique';
 Chaine1:=Chaine;
  // l'affectation ne pose pas de problème
 Chaine2:=Chaine;

  // Le passage en paramètre à une procédure const ou par valeur ne pose pas de problème
 Affiche(Chaine1);


  // Le passage en paramètre à une procédure var, provoque l'erreur (E2033) :
  // Les types des paramètres var originaux et formels doivent être identiques.
  Affiche_Var(Chaine1);

Le code précédent provoque l'erreur suivante :

 
Sélectionnez

E2033 : Les types des paramètres var originaux et formels doivent être identiques.

3. Nouveaux spécificateurs de visibilité pour les membres de classe

Delphi ajoute 2 spécificateurs de visibilité supplémentaires strict private (privée stricte) et strict protected (protégée stricte). Ils sont ordonnés ainsi, bien que dans une déclaration de classe cette notion d'ordre ne soit effective :

  • strict private
  • private
  • strict protected
  • protected
  • public

Texte issu de la documentation de DELPHI 2006 beta
Les membres de classes dont la visibilité est strict private ne sont accessibles que dans la classe dans laquelle ils sont déclarés. Ils ne sont pas visibles pour les procédures ou fonctions déclarées dans la même unité.

Les membres de classes dont la visibilité est strict protected sont visibles dans la classe dans laquelle ils sont déclarés et dans toutes les classes dérivées, quel que soit l'endroit où elles sont déclarées.
Rappel :
Les membres au début de la déclaration d'une classe dont la visibilité n'est pas spécifiée sont par défaut public.

 
Sélectionnez

  TMaClasse = class

  strict private
     // Visible seulement par les instances de la classe MaClasse
    FStrictPrivate: Integer;

  private
     // Visible seulement par les instances de la classe MaClasse ou de l'unité
    Fprivate: Integer;

  strict protected
     // Visible seulement par les instances de la classe MaClasse ou des classes dérivées
    FStrictProtected: Integer;

  protected
     // Visible seulement par les instances de la classe MaClasse, des classes dérivées ou de l'unité
    FProtected: Integer;

  public
     // Visible par tout code qui dispose d'un accès global à cette classe.
    FVisible: Integer;

  published
     // Rend publique cette partie et génère également les informations de types à l'exécution.
     // Entre autres rôles, les informations de types à l'exécution permettent à l'inspecteur
     // d'objets d'accéder aux propriétés et aux événements.
    property Visible: Integer read FVisible;
  end;

Vous pouvez, dans une classe, déclarer un constructeur privé afin d'empêcher la création d'instance de cette classe.

4. Variable de classe

Texte issu de la documentation de DELPHI 2006 beta

Les champs de classe sont des champs de données d'une classe accessibles sans une référence d'objet.
La déclaration de bloc class var vous permet d'introduire un bloc de champs de classe dans une déclaration de classe.

Un bloc class var se termine par :

  • Une autre déclaration class var
  • Une déclaration de procédure ou fonction (c'est-à-dire une méthode) (y compris des procédures de classe et des fonctions de classe)
  • Une déclaration de propriété (y compris des propriétés de classe)
  • Une déclaration de constructeur ou de destructeur
  • Un spécificateur de portée de visibilité (public, private, protected, published, strict private et strict protected)

Par exemple :

 
Sélectionnez

   TDBBool = record
    Private
        // Champ privé pour stocker la valeur du type
      FValeur : integer;
      Constructor Create(AValeur: IDBType);
      procedure SetValeur(AValeur: IDBType);

    Public
        // Champs statiques de classe.
      Class var dbFalse: TDBBool;
      Class var dbNull : TDBBool;
      Class var dbTrue : TDBBool;
...

Il est préférable de répéter la déclaration de class var, car dans certains cas vous rencontrerez quelques problèmes :

 
Sélectionnez

 Public 
        // champs de classe, static 
      Class var 
        dbFalse: TDBBool; 
        dbNull : TDBBool; 
        dbTrue : TDBBool; 
      // private 
       FValeur : integer; 

La mise en commentaire de Private provoque un effet de bord.


Le code suivant permet d'accéder aux champs de classe, déclarés ci-dessus, en préfixant le nom du champ avec le nom de la classe propriétaire :

 
Sélectionnez

Begin
  TDBBool.dbNull.FValeur:=cdbNull;
  TDBBool.dbFalse.FValeur:=cdbFalse;
  TDBBool.dbTrue.FValeur:=cdbTrue;
end;

Les variables de classe sont accessibles dans les classes dérivées :

 
Sélectionnez

Type
 TMaClass= Class
   VariableInstance :String;
   class var VariableDeClasse_TMaClass :String;
 end;

 TDescendant= Class(TMaClass)
   class var VariableDeClasse_TDescendant :String;
 end;

var Test          : TMaClass;
    TestDescendant: TDescendant;

begin
 Test.VariableDeClasse_TMaClass:='Test';
 TDescendant.VariableDeClasse_TDescendant:='Descendant';
 TestDescendant.VariableDeClasse_TDescendant:='Autre appel possible';
 
 TDescendant.VariableDeClasse_TMaClass:='Modification';
end.

5. Propriété de classe

Les propriétés de classe sont des propriétés d'une classe accessibles sans une référence d'objet.
Une propriété de classe ne peut pas être publiée et ne peut pas avoir de définition de valeur stockée ou par défaut. De plus le champ associé à la propriété de classe doit être au minimum un champ de classe et les possibles accesseurs de propriétés de classes doivent être déclarés comme méthodes statiques de classe.

Le non-respect de ces deux règles provoquera l'erreur de compilation suivante :

 
Sélectionnez

E2355 : L'accesseur de la propriété de classe doit être un champ de classe ou une méthode statique de classe.

Voici un exemple d'utilisation :

 
Sélectionnez

type
 TMaClass = class
  strict private
   class var FY : Integer;
   class var FX : Integer;

  strict protected
  // Remarque : les accesseurs des propriétés de classe doivent être déclarés comme méthodes de classe statiques.
   class function GetX: Integer; static;
   class procedure SetX(val: Integer); static;

  public
   class property X: Integer read GetX write SetX;
   class property Y: Integer read FY write FY;
 end;

class function TMaClass.GetX: Integer;
begin
 Result:=FX;
end;

class procedure TMaClass.SetX(val: Integer);
begin
 FX:=val;
end;

L'accés aux propriétés de classe se fait de la manière suivante :

 
Sélectionnez

begin
 TMaClass.X:=9;
 TMaClass.y:=7;
 Writeln(TMaClass.X,' ',TMaClass.Y);
 Readln;
end.

6. Surcharge de propriété indexée

Il est possible de surcharger la définition d'une propriété afin d'utiliser un ou plusieurs index de types différents. Si une classe a une propriété par défaut, vous pouvez accéder à cette propriété en utilisant la forme abrégée object[index] équivalente à object.property[index].

Par exemple, MonObjet.Strings[7] peut s'abréger en MonObjet[7]. Une classe ne peut avoir qu'une seule propriété par défaut.

 
Sélectionnez

type
 TMaClass = class
  FList : Array[1..10] of String;

  function GetItem(Index: string): string; overload;
  procedure SetItem(Index:string; Value: string); overload;

  function GetItem(Index: Integer): string; overload;
  procedure SetItem(Index: Integer; Value: string); overload;

  property Item[Index: string]: string
    read GetItem write SetItem; default;

  property Item[Index: integer]: string
    read GetItem write SetItem; default;
end;

Les deux propriétés sont déclarées avec le mot-clé default sinon il n'est pas possible de compiler cet exemple. Il semblerait que la surcharge ne soit possible que pour la propriété par défaut d'une classe.

Voici un exemple d'appel :

 
Sélectionnez

var Propriete : TMaClass;

begin
 Propriete:=TMaClass.Create;
 Propriete[1]:='Premier';
 Affiche(Propriete);

 Writeln('Accès integer '+Propriete[1]);
 Writeln('Accès String '+Propriete['Premier']);
 readln;

 Propriete['Premier']:='Deux';
 ...
end; 

7. Méthode Final

Cette nouvelle fonctionnalité permet à Delphi d'utiliser le concept de méthode virtuelle finale. Quand le mot clé final est appliqué à une méthode virtuelle, aucune classe dérivée ne peut redéfinir cette méthode.
L'usage du mot clé final est une décision de conception importante qui permet de définir l'utilisation de la classe.

 
Sélectionnez

program MethodeFinal;

{$APPTYPE CONSOLE}


uses
  SysUtils;

type
  TMaClass= class (TObject)
   Champ1 : integer ;
   procedure FaitqqChose; virtual;
  end;

  TDescendantClass= class (TMaClass)
   Champ2 : integer ;
   procedure FaitqqChose; override; Final;
  end;

  TClassRedeclareMethodeFinal= class (TDescendantClass)
   Champ3 : integer ;
   procedure FaitqqChose; override;   //Erreur
                                      // E2352 : Impossible de redéfinir une méthode finale 
  end;

procedure TMaClass.FaitqqChose;
begin
 Writeln('Champ1 ',Champ1);
end;

procedure TDescendantClass.FaitqqChose;
begin
 Writeln('Champ2 ',Champ2);
end;

var
  MonInstance: TClassRedeclareMethodeFinal;

begin
  MonInstance:=TClassRedeclareMethodeFinal.Create;
end.

L'ajout du mot clé final à la déclaration de la méthode FaitqqChose; override de la classe TDescendantClass provoque l'erreur de compilation suivante pour la déclaration de la classe TClassRedeclareMethodeFinal :

 
Sélectionnez

E2352 : Impossible de redéfinir une méthode finale.

8. Méthode statique de classe

Dans la version béta de Delphi 2006 le comportement de cette directive pose quelques problèmes.

Texte issu de la documentation de DELPHI 2006 beta
Les méthodes statiques de classe sont des méthodes d'une classe accessibles sans une référence d'objet. Elles fonctionnent de la même manière que les méthodes traditionnelles de classe sous Delphi Win32.

Pour rendre une méthode de classe statique, ajoutez le mot static à sa déclaration, par exemple

 
Sélectionnez

program MethodeStatic;
{$APPTYPE CONSOLE}

Type
 TMaClass= Class
   VariableInstance :String;
   class var VariableDeClasse :String;
   Class procedure Affiche2; // Méthode de classe
   Class procedure Affiche; static; // Méthode statique de classe
   procedure Traitement;
 end;

Class procedure TMaClass.Affiche;
begin
 VariableDeClasse :='Affiche : une seule occurence pour cette variable de classe';

 // La ligne suivante est gérée par le compilateur et n'est pas possible.
 //VariableInstance :='Plusieurs occurences pour cette variable d''instance.';

 Writeln(VariableDeClasse);
 //Writeln(Self.ClassName); // E2003 : Identificateur non déclaré
end;

Class procedure TMaClass.Affiche2;
begin
 //VariableDeClasse :='Affiche2 : une seule occurence pour cette variable de classe';
 Writeln(VariableDeClasse);

 if assigned(Self)
  then Writeln(Self.ClassName);
end;

procedure TMaClass.Traitement;
begin
  Writeln('Dans la méthode traitement');
end;

var Test : TMaClass;

begin
  // Appel de méthode de classe.
 TMaClass.Affiche2;
 Test.Affiche2;
 Test.Affiche;
 Test:=TMaClass.Create;  // Self accessible dans Affiche2
 Test.Affiche2;
 Readln;
end.

Contrairement aux méthodes de classe Delphi Win32, les méthodes statiques de classe n'ont pas de paramètre Self et ne peuvent donc accéder à aucun membre de classe, en dehors des champs de classe. En outre les méthodes statiques de classe ne peuvent pas être déclarées virtuelles (Virtual).

9. Classe scellée (sealed)

Cette nouvelle fonctionnalité permet à Delphi de déclarer une classe qui ne peut pas être étendue par le biais de l'héritage.
Une fois une classe déclarée scellée (sealed) elle ne peut plus être dérivée comme le montre l'exemple suivant :

 
Sélectionnez

program ClasseSealed;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  TMaClass= class (TObject)
   Champ1 : integer ;
   procedure FaitqqChose; virtual;
  end;

  TSealedClass= class sealed (TMaClass)
   Champ2 : integer ;
   procedure FaitqqChose; override;
  end;

   // Erreur à la compilation
  TImpossibleClass= class (TSealedClass)
   Champ3 : integer ;
   procedure FaitqqChose; override;
  end;

procedure TmaClass.FaitqqChose;
begin
 Writeln('Champ1 ', Champ1);
end;

procedure TSealedClass.FaitqqChose;
begin
 Writeln('Champ2 ', Champ2);
end;

var
 MonInstance: TSealedClass;

begin
  MonInstance:=TSealedClass.Create;
end.

L'ajout du mot clé sealed dans la déclaration de classe TSealedClass= class (TMaClass) provoque l'erreur de compilation suivante pour la déclaration de la classe TImpossibleClass :

 
Sélectionnez

E2353 : Impossible d'étendre la classe sealed 'TSealedClass' 

10. Classe abstraite

Dans la version béta de Delphi 2006 le mot clé abstract est reconnu par le compilateur mais son ajout ne modifie pas le comportement de la classe.

Cette nouvelle fonctionnalité permet à Delphi de déclarer la totalité d'une classe comme classe abstraite (abstract) ce qui implique que cette classe ne peut pas être instanciée. Une classe entière peut être déclarée abstraite même si elle ne contient aucune méthode virtuelle abstraite.Une classe entière peut être abstrait avoué même si elle ne contient aucune méthode virtuelle abstraite.
La syntaxe de déclaration est la suivante :

 
Sélectionnez

program Abstract;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  TAbstractClass= class abstract (TObject)
   ChampExistant : integer ;

   procedure FaitqqChose;
  end;

procedure TAbstractClass.FaitqqChose;
begin
 Writeln('ChampExistant ',ChampExistant);
end;

var
  MonInstance:TAbstractClass ;

begin
  MonInstance:=TAbstractClass.Create; // Impossible !
end.

La compilation de ce code, incluant une création d'une instance de classe abstraite, provoque l'erreur suivante :

 
Sélectionnez

E2402 : Construction de l'instance de la classe abstraite 'TAbstractClass'.

Il n'est pas possible de déclarer une classe abstract sealed :

 
Sélectionnez

type
  TAbstractClass= class abstract sealed (TObject)
   ChampExistant : integer ;

   procedure FaitqqChose;
  end;

Provoque l'erreur suivante :

 
Sélectionnez

E2383 : ABSTRACT et SEALED ne peuvent être utilisés ensemble.

11. Syntaxe d'assistance de classe (Class Helper)

Texte issu de la documentation de DELPHI 2006 beta
Une assistance de classe est un type qui, lorsqu'il est associé à une autre classe, introduit des méthodes et propriétés supplémentaires qui peuvent être utilisées dans le contexte de la classe associée (ou ses descendants). Les assistances de classes permettent d'étendre une classe sans avoir recours à l'héritage.

Les assistances de classes permettent d'étendre une classe, mais elles ne doivent pas être considérées comme un outil de conception à utiliser pour développer un nouveau code. Elles ne doivent être utilisées que pour la liaison entre le langage et la RTL de la plate-forme.

Leur création est due principalement aux problématiques de portage de la VCL sous .NET. Pour plus d'informations consultez le tutoriel Le langage Delphi pour .NET.

11-1. Un possible exemple d'utilisation

Lors de la création d'une instance de classe dans un bloc With do begin end, on aimerait récupérer une référence sur cette instance transitoire pour la communiquer à un traitement. Si on peut accéder à ces membres on ne peut malheureusement pas accéder à l'instance elle-même.

Sous Delphi 2005 et 2006 l'utilisation de Class Helper peut être une solution, bien que l'utilisation d'une variable locale soit la solution la plus trivial.
Tout comptes fait une classe Delphi peut manipuler l'instance via le mot clé self mais uniquement à l'intérieur d'une méthode. L'idée est donc d'ajouter une fonction membre qui renvoi self comme résultat.

 
Sélectionnez

Type 

  GetterClassHelper=Class Helper For TObject 
    Function  GetInstance:TObject; 
  end; 

   function GetterClassHelper.GetInstance:TObject;
   begin
     // Self fait référence à la variable en cours,
     // et non à une possible instance de type GetterClassHelper.
    Result:=Self;
   end;

On choisi de modifier la classe TObject afin de bénéficier de cet ajout de méthodes sur toutes les classes ancêtres.

L'appel dans le bloc With-Do devenant :

 
Sélectionnez

begin 
 With TMonObjet.Create do 
  begin 
   ... 
   Traitement(GetInstance); // Récupére la référence 
   ... 
  end; 
end.

Voici un programme de test :

 
Sélectionnez

program classhelper;

{$APPTYPE CONSOLE} 

uses
  SysUtils;

Type
  TTest=Class
   Nom: String;
  end;

  GetterClassHelper=Class Helper FOR TObject
    Function WriteClassName:ShortString;
    Function  GetInstance:TObject;
  end;

  Function GetterClassHelper.WriteClassName:ShortString;
   begin
      // Self fait référence au type Test, et non à GetterClassHelper.
     Result:=Self.ClassName;
   end;

   function GetterClassHelper.GetInstance:TObject;
   begin
     // Self fait référence à la variable en cours,
     // et non à une possible instance de type GetterClassHelper.
    Result:=Self;
   end;

Procedure Visualise(AInstance :TObject; NomVaraible:String);
begin
 Writeln('Nom de la variable : ',NomVaraible);
 Writeln;
 Writeln('Instance de la classe '+ TTest(AInstance).WriteClassName+' dans la methode visualise : '+TTest(AInstance).Nom);
 Writeln;
end;

Var Premier, Second : TTest;

begin
 Premier:=TTest.Create;
 Premier.Nom:='Je reference l''instance Premier';
 visualise(Premier,'Premier');

  // Teste si on récupére bien une référence sur l'instance Premier
  // équivaut à :
  //  Second:=Premier
 Second:=TTest(Premier.GetInstance);
 visualise(Second,'Second');
 Readln;

 Premier.Free;
 Second:=Nil;

  // Objectif final sur une classe personelle
 With TTest.Create do
  begin
   Nom:='Je reference l''instance transitoire dans le bloc With-do';
   visualise(GetInstance,' ici elle n''a pas de nom');
   Free;
  end;
 Readln;

  // Objectif final sur une classe de la VCL
 With TStringList.Create do
  begin
   visualise(GetInstance,' ici elle n''a pas de nom');
   Free;
  end;
 Readln; 
end.

Les dernières lignes nous montrent bien qu'un assistant de classe peut nous aider à compléter une hiérarchie de classe sans modifier le code source de la classe d'origine.
Cela peut être une solution pour compléter des classes pour lesquelles on ne dispose que des déclarations de la partie interface.

Dans notre exemple le code suivant ne pourra pas compiler car Self n'a pas d'existence dans ce programme console :

 
Sélectionnez

 With TTest.Create do 
  begin 
   Nom:='Je reference l''instance transitoire dans le bloc With-do'; 
   visualise(self,' Dans notre cas self n''existe pas'); 
   Free;
  end; 

Si dans une méthode d'une classe on utilise le mot clé self dans le bloc With do begin end, self fera référence à l'instance courante de la classe comportant la méthode en cours d'exécution, et non à l'instance de l'objet créé à la volée dans le bloc with.

Merci à Sylvain james pour cette dernière remarque.

Les membres d'un assistant de classe peuvent être

  • des méthodes de classes,
  • des variables de classes,
  • des propriétés,

Mais un assistant de classe ne peut pas déclarer de champ supplémentaire, ni de méthode virtual.
Il n'est pas possible de déclarer un assistant de classe d'un assistant de classe.
On peut déclarer plusieurs assistants de classe pour une même classe mais il semble que seul le dernier déclaré soit pris en compte et annule les précédents.

 
Sélectionnez

 TDescellee= class Helper For TSealedClass
   class var unChampDeClass : integer ;
   procedure FaitqqChose;
   Class procedure Affiche;
   Constructor Create;
 end;

Un assistant de classe peut être utilisé, comme le laisse supposer le dernier exemple, étendre une classe scellée (Sealed).

Notez que les assistants de classe ressemblent aux Extension Methods du C# 3.0.

12. Les records

L'utilisation des directives StrictPrivate, Private et Public est désormais possible dans un enregistrement.

Afin d'aborder rapidement le sujet déclarons dans une unité un type Record utilisant ces directives :

 
Sélectionnez

unit TestRecord;

interface

Type
 TUAdvancedRecord = record
  Strict private
   Valeur_StrictPrivate : integer;
  Private
   Valeur_Private : integer;
  Public
   Valeur_Public: Integer;
  end;

implementation

end.

Il nous reste à mettre en évidence les règles de portée que ces directives permettent en utilisant le programme suivant :

 
Sélectionnez

program NouveauRecord1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  TestRecord in 'TestRecord.pas';

type
 TAdvancedRecord = record
  Strict private
   Valeur_StrictPrivate : integer;
  Private
   Valeur_Private : integer;
  Public
   Valeur_Public: Integer;
  end;

var TypeRecordDansLaMemeUnite : TAdvancedRecord;
    TypeRecordDansUniteDifferente :TUAdvancedRecord;

begin

 with TypeRecordDansLaMemeUnite do
  begin
   //Valeur_StrictPrivate:=1; // error E2003 : Identificateur non déclaré 'Valeur_StrictPrivate'
   Valeur_Private:=2;
   Valeur_Public:=3;
  end;

 with TypeRecordDansUniteDifferente do
  begin
   //Valeur_StrictPrivate:=1; // error E2003 : Identificateur non déclaré 'Valeur_StrictPrivate'
   //Valeur_Private:=2; // error E2003 : Identificateur non déclaré 'Valeur_Private'
   Valeur_Public:=3;
  end;
end.

Les membres déclarés avec la directive :

  • Strict private sont accessibles uniquement par les membres du Record.
  • Private sont accessibles uniquement dans l'unité où le Record est déclaré.
  • Public sont accessibles dans tous le code.

12-1. Les records ont désormais de la classe

Le type record est désormais considéré soit comme traditionnel soit comme avancé.
La dénomination traditionnel concerne le type record qu'on connait depuis le Pascal, la dénomination avancé est un record ayant, sur certains point, un comportement plus proche d'une classe.

Déjà sous Delphi 2005 .NET le type record avait été modifié pour correspondre au principe qui veut que tout soit objet sous .NET. Leur existence sous Delphi 2005 Win32 n'ayant pas été documentée, c'est désormais chose faite.

Texte de la documentation de Delphi 2006 beta

 
Sélectionnez

En plus des types record traditionnels, Delphi autorise des types record plus complexe et plus proche d'une 
classe.
En plus des champs, les records peuvent avoir des propriétés et des méthodes (y compris des constructeurs),
 des propriétés de classe, des méthodes de classe, des champs de classe, et des types imbriqués. 

Voici une définition d'un type record simple avec certaines fonctionnalités de classes.
(note : le texte d'origine utilise l'expression anglaise 'class-like')
 
Sélectionnez

 type
  TMyRecord = record
    type
      TInnerColorType = Integer;
    var
      Red: Integer;
    
    class var Blue: Integer;
    
    procedure printRed();
    constructor Create(val: Integer);
    
    property RedProperty: TInnerColorType read Red write Red;
    
    class property BlueProp: TInnerColorType read Blue write Blue;
end;

constructor TMyRecord.Create(val: Integer);
begin
  Red := val;
end;

procedure TMyRecord.printRed;
begin
  writeln('Red: ', Red);
end;
 
Sélectionnez

Cependant les records peuvent maintenant partager une grande partie des fonctionnalités des classes, 
il y a quelques différences importantes entre les classes et les records.

- Les records ne supportent pas l'héritage.

- Les records peuvent contenir des parties variables, les classes ne le peuvent pas.

- Les records sont des types valeur, ainsi ils sont copiés lors d'affectation, passés par valeur, 
et alloués sur la pile à moins qu'ils soient déclarés globalement ou explicitement alloués en utilisant
les fonctions New et Dispose. Les classes sont des types référence, elles ne sont donc pas copiées lors 
d'une affectation, elles sont passées par référence, et elles sont allouées sur le tas (Heap).

- Les records permettent la surcharge d'opérateurs sur les plateformes Win32 et .NET; Les classes permettent la 
surcharge d'opérateurs seulement sous .NET. 

- Les records sont construits automatiquement, en utilisant un constructeur par défaut qui est sans argument, 
mais les classes doivent être explicitement construites. 
Puisque les records ont un constructeur par défaut, n'importe quel constructeur de record défini par 
l'utilisateur doit avoir obligatoirement un ou plusieurs paramètres.

- Les types record ne peuvent pas avoir de destructeurs. 

- Les méthodes virtuelles (celles indiquées avec les mots-clés virtual, dynamic, et message) ne peuvent pas être 
employées dans les types record. 

- À la différence des classes, les types record sur la plateforme Win32 ne peuvent pas implémenter d'interfaces;
Cependant les records sur la plateforme .NET le peuvent.

Désormais les record comme les classes peuvent utilisés les déclarations de type imbriqué.
Vous trouverez un exemple plus avant dans le chapitre sur la surcharge d'opérateur.

12-2. Surcharge d'opérateurs dans les enregistrements

Texte issu de la documentation de DELPHI 2006 beta
Delphi .NET autorise la surcharge de certaines fonctions (ou "opérateurs") dans les déclarations de classe et d'enregistrement. Le nom de la fonction opérateur correspond à une représentation symbolique dans le code source. Par exemple, l'opérateur Add correspond au symbole +. Le compilateur génère un appel à la surcharge appropriée, en faisant correspondre le contexte (c'est-à-dire le type de retour et le type des paramètres utilisés dans l'appel) à la signature de la fonction opérateur.
Le tableau suivant énumère les opérateurs Delphi pouvant être surchargés :

Opérateur Catégorie Signature de déclaration Mappage de symbole
Implicit Conversion Implicit(a : type): resultType; implicit typecast
Explicit Conversion Explicit(a: type): resultType; explicit typecast
Negative Unaire Negative(a: type): resultType; -
Positive Unaire Positive(a: type): resultType; +
Inc Unaire Inc(a: type): resultType; Inc
Dec Unaire Dec(a: type): resultType Dec
LogicalNot Unaire LogicalNot(a: type): resultType; not
BitwiseNot Unaire BitwiseNot(a: type): resultType; not
Trunc Unaire Trunc(a: type): resultType; Trunc
Round Unaire Round(a: type): resultType; Round
Equal Comparaison Equal(a: type; b: type): Boolean; =
NotEqual Comparaison NotEqual(a: type; b: type): Boolean; <>
GreaterThan Comparaison GreaterThan(a: type; b: type) Boolean; >
GreaterThanOrEqual Comparaison GreaterThanOrEqual(a: type; b: type): resultType; >=
LessThan Comparaison LessThan(a: type; b: type): resultType; <
LessThanOrEqual Comparaison LessThanOrEqual(a: type; b: type): resultType; <=
Add Binaire Add(a: type; b: type): resultType; +
Subtrac Binaire Subtract(a: type; b: type): resultType; -
Multiply Binaire Multiply(a: type; b: type): resultType; *
Divide Binaire Divide(a: type; b: type): resultType; /
IntDivide Binaire IntDivide(a: type; b: type): resultType; div
Modulus Binaire Modulus(a: type; b: type): resultType; mod
ShiftLeft Binaire ShiftLeft(a: type; b: type): resultType; shl
ShiftRight Binaire ShiftRight(a: type; b: type): resultType; shr
LogicalAnd Binaire LogicalAnd(a: type; b: type): resultType; et
LogicalOr Binaire LogicalOr(a: type; b: type): resultType; ou
LogicalXor Binaire LogicalXor(a: type; b: type): resultType; xor
BitwiseAnd Binaire BitwiseAnd(a: type; b: type): resultType; et
BitwiseOr Binaire BitwiseOr(a: type; b: type): resultType; ou
BitwiseXor Binaire BitwiseXor(a: type; b: type): resultType; xor

Aucun autre opérateur ne peut être défini sur un enregistrement.

Les méthodes d'opérateurs surchargés ne peuvent pas être référencées par nom dans le code source :

 
Sélectionnez

   y := x + x;   // Appelle TMaClasse.Add(a, b: TMaClasse): TMaClasse
   y := x add x ; // Interdit !
   y := TMaClasse.add(x, x); // Interdit !

Pour accéder à une méthode d'opérateur spécifique d'une classe spécifique, vous devez utiliser des transtypages explicites sur tous les opérandes :

 
Sélectionnez

  A,Z : integer
Begin
   A:= Z+TMaClasse(x);

Il n'existe aucune hypothèse concernant les propriétés distributives ou commutatives de l'opération. Pour les opérateurs binaires, le premier paramètre est toujours l'opérande gauche et le second paramètre l'opérande droit. En l'absence de parenthèses explicites, l'associativité est supposée être de gauche à droite.
En règle générale, les opérateurs ne doivent pas modifier leurs opérandes. A la place, ils renvoient une nouvelle valeur, construite en effectuant l'opération sur les paramètres.

12-3. Déclaration des surcharges d'opérateurs

Les surcharges d'opérateurs sont déclarées dans des enregistrements avec la syntaxe suivante :

 
Sélectionnez

type
  typeName = [record]
      class operator Signature_de_déclaration (a: type): Type_résultant;
      liste d'opérateur	
  end;

L'implémentation des opérateurs surchargés doit aussi inclure la syntaxe class operator :

 
Sélectionnez

 class operator Name._de_type. Signature _de_déclaration (a: type): Type_résultant;

Exemples d'opérateurs surchargés pour un enregistrement permettant de simuler un type :

 
Sélectionnez

unit UTDBBool;

interface

type

   TDBBool = record
   Strict private
        // Champ privé pour stocker la valeur du type
      FValeur : integer;

        // champs de classe
      Class var FdbFalse : TDBBool;
      Class var FdbNull  : TDBBool;
      Class var FdbTrue  : TDBBool;

         // Type interne et Privé
        type
           DBType = -1..1;  // Etendue du type

     Private
      // Le constructeur ne doit pas être accessible
      // Private pour TDBBool.dbTrue:=TDBBool.Create(0);
      Constructor Create(AValeur: DBType);
        // Constantes internes et Privées
      const
        // Valeurs du type autorisé
        // On ne reprend pas à l'identique les valeurs du type Boolean de Delphi
        // Le zéro permet les tests x>0 pour true et x<0 pour false, voir aussi l'opérateur LogicalNot
       cdbNull   : integer =0;
       cdbFalse  : Integer =-1;
       cdbTrue   : Integer =1;

      Class procedure Init;Static;
     Public
       // Examine la valeur d'un DBBool.
       // Renvoie true si la valeur de Fvaleur correspond à la valeur testé
      Function IsNull  : Boolean;
      Function IsFalse : Boolean;
      Function IsTrue  : Boolean;

        // Surcharge d'opérateur
      Class operator Implicit(Avalue: Boolean): TDBBool;
      Class operator Implicit(x: TDBBool): String;
      Class operator Explicit(x: TDBBool): Boolean;
      Class operator Equal(x,y : TDBBool):TDBBool; //  =
      Class operator NotEqual(x,y : TDBBool):TDBBool; // <>
      Class operator LogicalNot(x : TDBBool):TDBBool; // not
      Class operator LogicalAnd(x,y : TDBBool):TDBBool; // and
      Class operator LogicalOr(x,y : TDBBool):TDBBool; // or

        // Convertit la valeur spécifiée en sa représentation String équivalente.
      Function ToString: String;
       // Propriétées en ReadOnly
      Class Property dbFalse : TDBBool Read FdbFalse;
      Class Property dbNull  : TDBBool Read FdbNull;
      Class Property dbTrue  : TDBBool Read FdbTrue;
    end;

implementation

uses Classes;

   Constructor TDBBool.Create(AValeur: DBType);
   Begin
    FValeur:=AValeur;
   end;

   Class Procedure TDBBool.Init;
   Begin
    TDBBool.FdbNull.FValeur:=cdbNull;
    TDBBool.FdbFalse.FValeur:=cdbFalse;
    TDBBool.FdbTrue.FValeur:=cdbTrue;
   end;

   Function TDBBool.IsNull  : Boolean;
   begin
    Result:=(Fvaleur=cdbNull);
   end;

   Function TDBBool.IsFalse : Boolean;
   begin
    Result:=(Fvaleur=cdbFalse);
   end;

   Function TDBBool.IsTrue  : Boolean;
   begin
    Result:=(Fvaleur=cdbTrue);
   end;

   //  implémentation des Opérateurs

    // Conversion implicite de boolean vers TDBBool.
    // renvoie DBBool.FdbTrue pour true
    // et DBBool.FdbFalse pour false
   Class operator TDBBool.Implicit(Avalue: Boolean): TDBBool;
   begin
    If Avalue=True
     then Result:=FdbTrue
     else Result:=FdbFalse;
   end;


    // Conversion explicite de TDBBool vers boolean.
    // Déclenche une exception si x est égal à cdbNull,
    // sinon renvoie True ou False.
   Class operator TDBBool.Explicit(x: TDBBool): Boolean;
   begin
    If x.FValeur=cdbNull
     then raise EInvalidOperation.Create('TDBOOL:opération invalide')
     else Result:= x.FValeur>cdbNull // x.FValeur=cdbTrue;
   end;

    // Opérateur d'égalité. Renvoi FdbNull si l'une des opérandes est égale à cdbNull,
    // sinon renvoie FdbTrue ou FdbFalse:
   Class operator TDBBool.Equal(x : TDBBool; y : TDBBool):TDBBool;
    // ATTENTION : pas d'évaluation booléenne complète, cf C#
   begin
     if ((x.FValeur = cdbNull) or (y.FValeur =cdbNull))
      then result:=FdbNull
      else if x.FValeur = y.FValeur
            then Result:=FdbTrue
            else Result:=FdbFalse;
   end;

    // Opérateur d'inégalité. Renvoie FdbNull si l'une des opérandes est égale à FdbNull,
    // sinon renvoie FdbTrue ou FdbFalse:
   Class operator TDBBool.NotEqual(x,y : TDBBool):TDBBool;
    // ATTENTION : pas d'évaluation booléenne complète, cf C#
   Begin
     if ((x.FValeur = cdbNull) or (y.FValeur =cdbNull))
      then result:=FdbNull
      else if x.FValeur <> y.FValeur
            then Result:=FdbTrue
            else Result:=FdbFalse;
   end;

    // Opérateur de négation logique. Renvoie  :
    //  FdbTrue si l'opérande est égale à FdbFalse
    //  FdbNull si l'opérande est égale à FdbNull,
    //  ou FdbFalse si l'opérande est égale à FdbTrue.
   Class operator TDBBool.LogicalNot(x : TDBBool):TDBBool;
   begin
      Result:=TDBBool.Create(-x.FValeur);
   end;

    // Opérateur ET ( AND ) logique. Renvoie
    //  FdbFalse si l'une des opérandes est égale à FdbFalse,
    //  FdbNull si l'une des opérandes est égale à FdbNull,
    //  sinon renvoie FdbTrue.
   Class operator TDBBool.LogicalAnd(x,y : TDBBool):TDBBool;
   var Resultat:Integer;
   begin
     If x.FValeur < y.FValeur
      then Resultat:=x.FValeur
      else Resultat:=y.FValeur;
     Result:=TDBBool.Create(Resultat);
   end;

    // Opérateur ET ( AND ) logique. Renvoie
    //  FdbTrue si l'une des opérandes est égale à FdbTrue,
    //  FdbNull si l'une des opérandes est égale à FdbNull,
    //  sinon renvoie FdbFalse.
   Class operator TDBBool.LogicalOr(x,y : TDBBool):TDBBool;
   var Resultat:Integer;
   begin
     If x.FValeur > y.FValeur
      then Resultat:=x.FValeur
      else Resultat:=y.FValeur;
     Result:=TDBBool.Create(Resultat);
   end;

    // Conversion implicite de TDBBool vers String.
   Class operator TDBBool.Implicit(x: TDBBool): String;
   begin
      If x.Fvaleur > 0
       then Result:='dbTrue'
       else If x.Fvaleur < 0
             then Result:='dbFalse'
             else Result:='dbNull';
   end;

    // Surcharge la méthode ToString pour convertir un TDBBool vers une String.
   Function TDBBool.ToString: String;
   begin
      Case Fvaleur of
        -1: Result:='TDBBool.False';
         0: Result:='TDBBool.Null';
         1: Result:='TDBBool.True';
       else Raise EInvalidOperation.Create('TDBOOL:opération invalide');
      end;
   End;


 // Initialise les valeurs possibles du type
Initialization
 TDBBool.Init;
end.

Les constructeurs de classe n'existant pas sous Win32 on utilisera la partie Initialization d'une unité pour simuler son comportement.

Voici l'implémentation d'un opérateur surchargé, qui ne modifie par ses opérandes mais renvoie une nouvelle valeur :

 
Sélectionnez

    // Opérateur ET ( AND ) logique. Renvoie
    //  dbFalse si l'une des opérandes est égale à dbFalse,
    //  dbNull si l'une des opérandes est égale à dbNull,
    //  sinon renvoie dbTrue.
   Class operator TDBBool.LogicalAnd(x,y : TDBBool):TDBBool;
   var Resultat:Integer;
   begin
     If x.FValeur < y.FValeur
      then Resultat:=x.FValeur
      else Resultat:=y.FValeur;
     Result:=TDBBool.Create(Resultat);
   end;

Le constructeur n'alloue pas de mémoire mais accède aux champs de l'enregistrement courant. Le programme de test de cet enregistrement :

 
Sélectionnez

program operateur;
{$APPTYPE CONSOLE}

uses
  SysUtils,
  UTDBBool in 'UTDBBool.pas',
  classes;

var
  a, b, c : TDBBool;

begin
  //Initialisation des variables hors constructeur
 a:=TDBBool.dbTrue; 
  // Assignation de la valeur de la variable a dans la variable b
 b:=a;              
 b:=TDBBool.dbNull;
 c:=TDBBool.dbFalse;

 WriteLn('a = ',a.ToString,' not a =', (not a).ToString); // Appel  : operator LogicalNot
 WriteLn('c =', c.ToString); // Appel  : operator LogicalOr
 WriteLn('b = ', b.ToString,' not b =', (not b).ToString);
 WriteLn('a and b =',(a and b).ToString); // Appel  : operator LogicalAnd
 WriteLn('a or b =', (a or b).ToString); // Appel  : operator LogicalOr

 Readln;
 
  // l'instruction case n'est pas possible sur un type record
 if b=TDBBool.dbTrue then
  Writeln('b est dbTrue')
 else
 if b=TDBBool.dbFalse then
  Writeln('b est dbFalse')
 else Writeln('b est dbNull');

  // a=c renvoi un résultat erroné 
  // if boolean(a=c) renvoi un résultat erroné 
 if boolean(a=c)=true
   then Writeln('A = C');

  //Valeur true ou false gérée
 try
  if boolean(a)=Boolean(c)  // Appel operator Explicit
   then Writeln('Boolean : a et c sont identiques')
   else Writeln('Boolean : a et c ne sont pas identiques');
 except
  On E:EInvalidOperation do
     WriteLn('a ou c est NULL')
 end;

  //Valeur Null non-gérée, mais opération valide
 try
  if Boolean(b)=True
   then WriteLn('b est true definitivement')
   else WriteLn('b n''est definitivement pas true ');
 except
  On E:EInvalidOperation do
      WriteLn('b est NULL');
 end;

  //Valeur Null non-gérée, mais opération valide
 try
  if boolean(a)=Boolean(b)
   then Writeln('a et b sont identiques')
   else Writeln('a et b ne sont pas identiques');
 except
  On E:EInvalidOperation do
     WriteLn('b est NULL')
 end;

 Readln;

(* Résultats attendu :
not DBBool.True = DBBool.False
not DBBool.Null = DBBool.Null
DBBool.True & DBBool.Null = DBBool.Null
DBBool.True or DBBool.Null = DBBool.True

a et c ne sont pas identique
b est NULL
b est NULL
*)
end.

Pour accéder à une méthode d'opérateur spécifique d'une classe spécifique, vous devez utiliser des transtypages explicites sur tous les opérandes : if Boolean(b)=True

12-4. Les records dynamique

L'utilisation des enregistrements dynamique reste identique :

 
Sélectionnez

program NouveauRecord2;
{$APPTYPE CONSOLE}

uses
  SysUtils;

type
 PMyRec=^MyRec;
 MyRec=Record
  A,B :byte;
  Constructor Create(lA,lB:byte);
  Procedure Ecrit(Name:String);
 end;

Constructor MyRec.Create(lA,lB:byte);
begin
 A:=lA;
 B:=lB;
end;

Procedure MyRec.Ecrit(Name:String);
begin
 Writeln(Name,' A=',A,' B=',B);
end;

procedure visu;
var T: MyRec;
begin
 // T n'est pas initialisé
 T.Ecrit('T locale');
end;

var Un : MyRec;
    R  : PMyRec;
begin
  //Variable globale initialisée à Zéro
 Un.Ecrit('UN');
 visu;

 New(R);                    //Alloue une zone mémoire
 R^.Create(1,2);            //Le constructeur réaffecte la même zone mémoire
 Un.Create(1,2);            //Le constructeur réaffecte la même zone mémoire
 Un:=MyRec.Create(10,16);   //L'assignation réaffecte la même zone mémoire
 Dispose(R);                //Désalloue la zone mémoire
 R:=Nil;
 R^.Ecrit('R après NIL.');  //Violation d'accés
end.

Attention les variables globales sont initialisées à zéro, dans tout les autres cas vous devez appeler le constructeur ou initialiser les champs avant de manipuler un enregistrement.

13. Directives de compilation

13-1. Experimental

Il s'agit d'une directive de conseil. Vous pouvez utiliser cette directive pour désigner des unités qui sont dans un état de développement instable. Le compilateur émet un avertissement quand il construit une application qui utilise l'unité.

 
Sélectionnez

unit UMonObjet platform experimental; 

13-2. Description

De portée globale, sa syntaxe est :

 
Sélectionnez

 {$DESCRIPTION 'text'}

La directive $D insère le texte que vous avez spécifié dans l'entrée 'description de module' de l'entête d'un exécutable, d'une DLL, ou d'un package. Traditionnellement le texte est un nom, un numéro de version, une note de copyright, mais vous pouvez spécifier n'importe quel texte de votre choix.

Par exemple:

 
Sélectionnez

 {$D 'Mon Application version 12.5'}

La chaîne de caractères ne doit pas dépasser 256 octets. La description n'est habituellement pas visible par l'utilisateur final. Pour marquer votre fichier exécutable avec du texte descriptif, un numéro de version et des informations de copyright à l'usage des utilisateurs finaux, utiliser les ressources d'information de version.

Note:
Le texte doit être inclue entre quottes.

13-3. Extension de l'exécutable

Déjà présent sous Delphi 2005, sa syntaxe est :

 
Sélectionnez

 {$E extension} ou {$EXTENSION extension}

La directive $E définit l'extension du fichier exécutable généré par le compilateur. Elle est souvent utilisée avec le mécanisme DLL ressource seulement. Par exemple, placer {$E deu} dans un module bibliothèque génère une DLL ayant l'extension .deu : nomfichier.deu. Si vous créez un module bibliothèque qui ne référence que des chaînes et des fiches allemandes, vous pouvez utiliser cette directive pour produire une DLL ayant l'extension .deu.
Le code de démarrage de la bibliothèque d'exécution cherche une DLL dont l'extension correspond à la locale du système — pour l'allemand, il cherche .deu — et charge les ressources de cette DLL.

13-4. Directives de compilation pour les bibliothèques ou les objets partagés

Déjà présent sous Delphi 2005 et de portée globale, sa syntaxe est :

 
Sélectionnez

 $LIBPREFIX 'chaîne'
 
 $LIBSUFFIX 'chaîne'
 
 $LIBVERSION 'chaîne'

//exemple 

 $LIBPREFIX 'lib' ou $LIBPREFIX 'bpl' 

 $LIBSUFFIX ' '
 
 $LIBVERSION ' '

$LIBPREFIX remplace le préfixe 'lib' ou 'bpl' par défaut dans le nom du fichier de destination.
Par exemple, vous pouvez spécifier {$LIBPREFIX 'dcl'} pour un package de conception, ou utiliser la directive suivante pour éliminer le préfixe entièrement :

 
Sélectionnez

{$LIBPREFIX ' '}

{$LIBSUFFIX ' '} ajoute le suffixe spécifié au nom du fichier de destination, avant l'extension. Par exemple, utilisez :

 
Sélectionnez

{$LIBSUFFIX '-2.1.3'}

dans quelquechose.pas pour générer : quelquechose-2.1.3.dll

$LIBVERSION ajoute une seconde extension au nom du fichier de destination après l'extension. Par exemple, utilisez

 
Sélectionnez

{$LIBVERSION '-2.1.3'}

dans quelquechose.pas pour générer : libquelquechose.dll.2.1.3

13-5. Vérification des exceptions sur les flottant (Floating Point Exception Checking)

Switch de portée globale, sa syntaxe est

 
Sélectionnez

 {$FINITEFLOAT ON}, {$FINITEFLOAT OFF}

La directive $FINITEFLOAT contrôle les débordements sur les flottants (overflow et underflow), et les opérations invalides telles que la division par zéro.
Avec {$FINITEFLOAT ON}, qui est l'état par défaut, les résultats des calculs sur les flottants sont vérifiés, et une exception est déclenchée lorsqu'il y a un dépassement de capacités (overflow et underflow), ou une opération invalide.

Avec {$FINITEFLOAT OFF}, de tels calculs sur des flottants renverront NAN, -INF ou +INF.

Un temps de traitement supplémentaire est nécessaire pour vérifier les résultats des calculs sur des nombres à virgule flottante et pour déclencher des exceptions.
Si votre code Delphi utilise des opérations de virgule flottante mais n'exige pas l'application stricte des exceptions de dépassement de capacités (overflow et underflow), vous pouvez utiliser {$FINITEFLOAT OFF} pour obtenir une exécution légèrement plus rapide.

Note :
La plupart du code sous .NET fonctionne sans contrôles de virgule flottante. Cependant, Delphi a traditionnellement fourni la sémantique stricte des nombres à virgule flottante.
Si vous avez du code Delphi qui se base sur des exceptions de dépassement de capacités (overflow et underflow), vous devriez maintenir la directive par défaut ({$FINITEFLOAT ON}).

13-6. Informations de méthode

Switch de portée locale, sa syntaxe est

 
Sélectionnez

 {$METHODINFO ON} or {$METHODINFO OFF}

Par défaut à OFF.

La directive $METHODINFO est effective seulement lorsque les informations RTTI (runtime type information) ont été autorisées avec la directive {$TYPEINFO ON}.
Dans l'état {$TYPEINFO ON}, La directive $METHODINFO contrôle la génération de descripteurs de méthode plus détaillés dans le RTTI pour des méthodes dans une interface.

Bien que {$TYPEINFO ON} générera des informations RTTI pour des méthodes publiées, le niveau de l'information est limité. La directive $METHODINFO produit des informations RTTI beaucoup plus détaillées (et d'un nombre plus important) pour des méthodes, qui décrit comment les paramètres de la méthode devraient être passés sur la pile et/ou dans des registres.
Il est rare qu'une application utilise directement la directive $METHODINFO.
Les informations de méthodes augmentent considérablement la taille du fichier exécutable, et son usage n'est pas recommandée pour un utilisation courante.

Note:
Le support du code web service du compilateur Delphi Win32 utilise les informations de descripteurs de méthodes afin de passer des paramètres reçus dans une trame réseau (network packet) à la méthode cible. {$METHODINFO ON} est employé seulement pour des types d'interface web service.

Voir la directive {$TYPEINFO }.

13-7. Champs d'entête PE (portable executable)

Flag de porté local, sa syntaxe est :

 
Sélectionnez

 {$SetPEFlags <integer expression>} {$SetPEOptFlags <integer expression>} 

Microsoft s'appuie sur les drapeaux d'en-tête PE (portable executable) pour permettre à une application d'indiquer la compatibilité avec des services de l'OS ou de demander des services avancés de l'OS.
Ces directives fournissent des options puissantes pour optimiser vos applications sur les systèmes 'high-end' NT.

Il n'y a aucune vérification des erreurs ou des masques de bit indiquées par ces directives. Si vous placez une mauvaise combinaison, vous pouvez corrompre votre fichier exécutable.

Ces directives vous permettent respectivement de positionner un ensemble de flags dans le champ Characteristics et dans le champ facultatif DLLCharacteristics d'un en-tête de fichier PE. La plupart des flags Characteristics, ensemble utilisant $SetPEFlags, sont spécifiques aux fichiers et aux librairies. Les flags DLLCharacteristics, ensemble utilisant $SetPEOptFlags, décrivent quand appeler le point d'entrée d'une DLL. La partie '<integer expression> dans ces directives peut inclure des identifiants de constantes Delphi, telles que les constantes IMAGE_FILE_xxxx définies dans Windows.pas.
Les constantes multiples doivent être un ensemble construit avec l'opérateur OR.
Vous pouvez inclure plusieurs fois ces directives dans le code source. Les valeurs des flags indiquées par des directives multiples sont strictement cumulatives :
si la première occurrence de la directive affecte $03 et la deuxième occurrence affecte $10, la valeur écrite dans le fichier exécutable au moment de l'édition de lien sera $13 (plus quelque autres que l'éditeur de liens place normalement dans les flags PE).

Ces directives affectent seulement le fichier de sortie (output file) s'il est inclus dans le code source avant l'édition des liens. Ceci signifie que vous devriez placer ces directives dans un fichier .dpr ou de .dpk, mais pas dans un fichier d'unité. Comme la directive de description de l'exe, ce n'est pas une erreur de placer ces directives dans du code source d'une unité, mais ces directives dans le code source d'une unité n'affecteront pas le fichier de sortie (exe ou DLL) à moins que le source de l'unité soit recompilé lorsque le fichier de sortie est linké.

14. Conclusions

Ces évolutions, certaines déjà présentent dans Delphi 2005, faciliteront, entre autres, le développement de code partagé sous Win32 et .NET en évitant le recours massif aux directives de compilation conditionnelle.
Pour ma part j'aurais apprécié pour les Record dynamique une syntaxe facilitant l'allocation et l'appel du constructeur en une passe, un peu comme sous TP 7.0 :-), ainsi qu'une instruction similaire au FreeAndNil pour la désallocation.

Quoi qu'il en soit ces nouveautés du langage sont déjà fort appréciables et il me tarde de découvrir celle de Highlander qui s'annonce révolutionnaire pour Delphi mais chaque chose en son temps ;-).

15. Liens