Skip to content

Delphi Aide-mémoire

Dialecte Pascal orienté objet pour le développement rapide d'applications.

01

Structure de programme & Bases

Structure de programme & Unités

Un programme Delphi commence par 'program' et se termine par 'end.' (point). {$APPTYPE CONSOLE} est une directive de compilateur le marquant comme application console. 'uses' importe des unités (modules) — System.SysUtils contient Format, IntToStr, etc. Les unités ont une section interface (déclarations publiques) et une section implementation (code). WriteLn produit du texte avec un saut de ligne ; Write produit sans. ReadLn lit l'entrée (ou met en pause). Le bloc begin..end principal est le point d'entrée du programme.

delphi
program Demo;          // program keyword starts a console app

{$APPTYPE CONSOLE}      // compiler directive: console application

uses                    // import units (like #include or import)
  System.SysUtils,      // utilities (Format, IntToStr, etc.)
  System.Classes;       // TList, TStrings, etc.

// a unit has interface (declarations) and implementation (bodies)
// unit Math;
// interface
//   function Add(A, B: Integer): Integer;
// implementation
//   function Add(A, B: Integer): Integer;
//   begin Result := A + B; end;
// end.

var
  Name: string;
begin
  Name := 'Alice';
  WriteLn('Hello, ', Name, '!');    // WriteLn = output with newline
  Write('No newline');              // Write = output without newline
  ReadLn;                           // wait for Enter (pause)
end.

Variables, Types & Constantes

Delphi est fortement typé. Types courants : Integer (32 bits), Int64 (64 bits), Double (flottant 64 bits), Extended (flottant 80 bits sur x86), Single (flottant 32 bits), string (Unicode, à comptage de références), Char (WideChar, 2 octets), Boolean, Byte (0-255). TDateTime est en réalité un Double (jours depuis 1899-12-30). Les constantes utilisent 'const' — les constantes typées ont un type, les non typées sont flexibles. Les types sous-intervalle (0..150) restreignent les valeurs. Les énumérations (TDay) définissent des constantes nommées. Format() est comme sprintf : %s (chaîne), %d (entier), %f (flottant).

delphi
var
  Name: string = 'Alice';        // string (managed, reference-counted)
  Age: Integer = 30;             // 32-bit signed integer
  BigNum: Int64 = 9223372036854775807;  // 64-bit
  Pi: Double = 3.14159;          // 64-bit float (IEEE 754)
  E: Extended = 2.71828;         // 80-bit float (x87)
  Rate: Single = 0.5;            // 32-bit float
  Ch: Char = 'A';                // 2-byte Unicode char (WideChar)
  IsDev: Boolean = True;         // True/False
  Bytes: Byte = 255;             // 0..255
  Date: TDateTime;               // date/time (double)

// constants
const
  MaxRetries = 3;                // untyped constant
  Pi: Double = 3.14159265;       // typed constant
  Greeting: string = 'Hello';

// type aliases
type
  TAge = 0..150;                 // subrange type
  TDay = (Mon, Tue, Wed, Thu, Fri, Sat, Sun);  // enumeration

begin
  Date := Now;                   // current date/time
  WriteLn(Format('%s is %d', [Name, Age]));   // formatted output
  WriteLn('Pi = ', Pi:0:2);      // 3.14 (width:decimals)
end;

Opérateurs & Expressions

Delphi utilise := pour l'affectation et = pour l'égalité (contraire des langages de type C). div est la division entière ; / est la division réelle (retourne toujours Extended/Double). mod est le reste. and/or/not/xor fonctionnent à la fois sur les booléens (logique) et les entiers (bit à bit) — le contexte détermine lequel. shl/shr sont les décalages de bits. Inc/Dec sont des incrémentations/décrémentations sur place efficaces (évitez d'écrire A := A + 1). La concaténation de chaînes utilise +. Power() est dans System.Math. La distinction := vs = est la source n°1 d'erreurs des débutants.

delphi
var
  A, B: Integer;
  X, Y: Double;
  S1, S2: string;
begin
  A := 10; B := 3;
  WriteLn(A + B);    // 13 (addition)
  WriteLn(A - B);    // 7
  WriteLn(A * B);    // 30
  WriteLn(A div B);  // 3 (integer division)
  WriteLn(A mod B);  // 1 (remainder)
  WriteLn(A / B);    // 3.33 (real division, always Extended)

  X := 2.0; Y := 3.0;
  WriteLn(X + Y);    // 5.0
  WriteLn(Power(X, Y)); // 8.0 (needs System.Math)

  // comparison (return Boolean)
  WriteLn(A > B);    // TRUE
  WriteLn(A = B);    // FALSE (= is equality, not assignment!)
  WriteLn(A <> B);   // TRUE (not equal)
  WriteLn(A >= B);   // TRUE

  // logical operators
  WriteLn(True and False);  // FALSE
  WriteLn(True or False);   // TRUE
  WriteLn(not True);        // FALSE
  WriteLn(True xor False);  // TRUE

  // string concatenation
  S1 := 'Hello'; S2 := 'World';
  WriteLn(S1 + ', ' + S2 + '!');  // Hello, World!

  // bitwise (on integers)
  WriteLn(5 and 3);  // 1
  WriteLn(5 or 3);   // 7
  WriteLn(5 shl 1);  // 10 (shift left = *2)
  WriteLn(5 shr 1);  // 2 (shift right = /2)

  // Inc and Dec (modify in place)
  Inc(A);      // A := A + 1
  Inc(A, 5);   // A := A + 5
  Dec(A);      // A := A - 1
end;

Entrée, Sortie & Formatage

Format() est le sprintf de Delphi — utilise %s (chaîne), %d (entier), %f (flottant), %x (hex), %m (monnaie), avec des modificateurs de largeur/précision. WriteLn(value:width:decimals) formate les flottants directement. ReadLn lit l'entrée dans une variable. StrToInt/StrToFloat convertissent les chaînes en nombres (lèvent EConvertError en cas d'échec) ; TryStrToInt retourne un booléen et est plus sûr. IntToStr/FloatToStr convertissent les nombres en chaînes. FormatDateTime formate les dates (yyyy, mm, dd, hh, nn, ss). FloatToStrF donne un contrôle précis (ffFixed, ffCurrency, ffExponent).

delphi
var
  Name: string;
  Age: Integer;
  Salary: Double;
begin
  // console output
  WriteLn('Hello, World!');        // with newline
  Write('No newline');             // without
  WriteLn;                         // just a newline

  // formatted output with Format (like sprintf)
  WriteLn(Format('Name: %s, Age: %d', ['Alice', 30]));
  WriteLn(Format('Pi: %.4f', [3.14159]));      // Pi: 3.1416
  WriteLn(Format('Hex: %x', [255]));            // Hex: FF
  WriteLn(Format('Pad: %10d', [42]));           // right-aligned
  WriteLn(Format('Left: %-10d|', [42]));        // left-aligned
  WriteLn(Format('Money: %m', [1234.56]));      // currency

  // WriteLn with format specifiers (width:decimals)
  WriteLn(3.14159:0:2);    // 3.14
  WriteLn(42:5);           //    42 (width 5)

  // console input
  Write('Enter your name: ');
  ReadLn(Name);
  Write('Enter your age: ');
  ReadLn(Age);

  // type conversion functions
  Salary := StrToFloat('50000.50');
  Age := StrToInt('30');
  WriteLn(IntToStr(42));              // '42'
  WriteLn(FloatToStr(3.14));          // '3.14'
  WriteLn(FloatToStrF(3.14159, ffFixed, 8, 2));  // '3.14'
  WriteLn(FormatDateTime('yyyy-mm-dd', Now));    // '2024-06-18'

  // TryStrToInt (safe parsing)
  if TryStrToInt('123', Age) then
    WriteLn('Parsed: ', Age);
end;

Unités, Portée & Visibilité

Les unités sont les modules de Delphi. La section interface déclare ce qui est public (visible par les utilisateurs) ; implementation contient le code et peut avoir des types/variables privés. Les sections initialization/finalization s'exécutent au chargement/déchargement de l'unité (comme des constructeurs/destructeurs pour l'unité). Les variables déclarées dans interface sont globales ; dans implementation elles sont privées à l'unité. Les types dans interface sont publics ; dans implementation ils sont privés. Cette conception à deux sections impose l'encapsulation au niveau de l'unité. La clause 'uses' importe d'autres unités — résolvez les conflits de nommage avec UnitName.Identifier.

delphi
// Unit declaration
unit MathHelper;

interface

uses
  System.SysUtils;

// public types (visible to users of the unit)
type
  TCalculator = class
  public
    function Add(A, B: Integer): Integer;
  end;

// public constants
const
  Pi = 3.14159265358979;

// public variables
var
  Counter: Integer;

// public function declarations
function Multiply(A, B: Integer): Integer;

implementation

// private types (only visible inside this unit's implementation)
type
  TInternal = record
    Value: Integer;
  end;

// private variables
var
  InternalCount: Integer;

// function body (implementation)
function Multiply(A, B: Integer): Integer;
begin
  Result := A * B;
end;

function TCalculator.Add(A, B: Integer): Integer;
begin
  Result := A + B;
end;

initialization
  // runs when the unit loads
  Counter := 0;
  InternalCount := 0;

finalization
  // runs when the unit unloads (cleanup)
  // free resources here

end.
02

Flux de contrôle

If...Then...Else

If...Then...Else est le conditionnel de Delphi. CRITIQUE : pas de point-virgule avant 'else' — le point-virgule termine l'instruction, et else fait partie du if. Pour les branches multi-instructions, enveloppez dans begin..end (toujours pas de point-virgule avant else). and/or/not sont des opérateurs logiques (aussi bit à bit sur les entiers). Utilisez des parenthèses pour grouper les conditions : (A > 0) and (B > 0). La règle du point-virgule manquant avant else est l'erreur de syntaxe Delphi la plus courante pour les débutants.

delphi
var
  Score: Integer;
  Grade: string;
begin
  Score := 85;

  // simple if
  if Score >= 60 then
    WriteLn('Pass');

  // if-else (no semicolon before 'else'!)
  if Score >= 90 then
    Grade := 'A'
  else if Score >= 80 then
    Grade := 'B'
  else if Score >= 70 then
    Grade := 'C'
  else
    Grade := 'F';

  WriteLn('Grade: ', Grade);

  // multi-statement if (needs begin..end)
  if Score > 50 then
  begin
    WriteLn('Passed');
    WriteLn('Congratulations');
  end
  else
  begin
    WriteLn('Failed');
    WriteLn('Try again');
  end;

  // nested with logical operators
  if (Score >= 0) and (Score <= 100) then
    WriteLn('Valid score');
end;

Instruction Case (Switch)

Case est le switch de Delphi — fonctionne sur les types ordinaux (Integer, Char, énumération, sous-intervalle). Chaque branche peut être une valeur unique, une liste séparée par des virgules ('D', 'F') ou un intervalle (1..5). La clause else est le défaut. Case ne tombe PAS (contrairement à C). Pour les branches multi-instructions, utilisez begin..end. Case est plus propre que les if-else enchaînés pour les valeurs discrètes. Vous ne pouvez pas case sur des chaînes directement (utilisez if-else ou une recherche).

delphi
var
  Grade: Char;
  Day: Integer;
begin
  Grade := 'B';

  // case on ordinal (integer, char, enum)
  case Grade of
    'A': WriteLn('Excellent');
    'B': WriteLn('Good');
    'C': WriteLn('Average');
    'D', 'F': WriteLn('Poor');     // multiple values
  else
    WriteLn('Invalid grade');       // default (else clause)
  end;

  // case with ranges
  Day := 3;
  case Day of
    1..5: WriteLn('Weekday');       // range
    6, 7: WriteLn('Weekend');
  else
    WriteLn('Invalid day');
  end;

  // case with enums
  type TColor = (Red, Green, Blue);
  var C: TColor;
  C := Green;
  case C of
    Red: WriteLn('Stop');
    Green: WriteLn('Go');
    Blue: WriteLn('Relax');
  end;

  // case with multi-statement branches
  case Grade of
    'A': begin
           WriteLn('Excellent');
           WriteLn('Keep it up');
         end;
    'B': WriteLn('Good');
  end;
end;

Boucles For (To, Downto, In)

For...to itère en ascendant ; For...downto en descendant. La variable de boucle ne peut pas être modifiée à l'intérieur de la boucle. For...in (Delphi moderne) itère les tableaux, les chaînes (caractère par caractère), les ensembles et tout énumérable. Break sort de la boucle ; Continue passe à l'itération suivante. Il n'y a pas de pas intégré — utilisez un conditionnel ou une boucle while. La variable de boucle est indéfinie après la boucle (ne vous fiez pas à sa valeur). For...in est préféré pour les collections (plus propre, pas d'erreurs d'index).

delphi
var
  i: Integer;
  Fruits: array of string;
  S: string;
begin
  // for...to (ascending)
  for i := 1 to 5 do
    WriteLn(i);          // 1 2 3 4 5

  // for...downto (descending)
  for i := 5 downto 1 do
    WriteLn(i);          // 5 4 3 2 1

  // for with step (no built-in step — use a while or compute)
  for i := 0 to 9 do
    if i mod 2 = 0 then
      WriteLn(i);        // 0 2 4 6 8

  // nested loops
  for i := 1 to 3 do
    for j := 1 to 3 do
      Write(i * j, ' ');
  WriteLn;

  // for...in (iterates collections, modern Delphi)
  Fruits := ['apple', 'banana', 'cherry'];
  for S in Fruits do
    WriteLn(S);

  // for...in on string (iterates characters)
  for S in 'Hello' do
    Write(S, ' ');       // H e l l o
  WriteLn;

  // for...in on set
  type TDigits = set of 1..5;
  var D: TDigits := [1, 3, 5];
  for i in D do
    WriteLn(i);          // 1 3 5

  // Break and Continue
  for i := 1 to 100 do
  begin
    if i > 10 then Break;       // exit loop
    if i mod 2 = 0 then Continue; // skip to next iteration
    WriteLn(i);                  // 1 3 5 7 9
  end;
end;

While & Repeat...Until

While teste avant le corps (peut ne jamais s'exécuter) ; repeat...until teste après (s'exécute toujours au moins une fois). CRITIQUE : while continue tant que la condition est TRUE ; repeat s'arrête quand la condition est TRUE (logique opposée !). repeat...until n'a pas besoin de begin..end (c'est intrinsèquement un bloc). Utilisez while pour 'zéro ou plusieurs fois' et repeat pour 'une ou plusieurs fois'. Break sort ; Continue passe au test. while True avec Break est un idiome courant pour les boucles avec des conditions de sortie complexes.

delphi
var
  Count: Integer;
  Line: string;
begin
  // while: test BEFORE (may never run)
  Count := 0;
  while Count < 3 do
  begin
    WriteLn(Count);
    Inc(Count);
  end;
  // 0 1 2

  // repeat...until: test AFTER (runs at least once)
  Count := 0;
  repeat
    WriteLn(Count);
    Inc(Count);
  until Count >= 3;
  // 0 1 2

  // KEY DIFFERENCE: while continues while TRUE; until stops when TRUE
  // while X < 3 do ...  ==  repeat ... until X >= 3

  // repeat doesn't need begin..end (it's already a block)
  Count := 5;
  repeat
    WriteLn(Count);
    Dec(Count);
  until Count = 0;

  // reading input until a condition
  repeat
    Write('Enter "quit" to stop: ');
    ReadLn(Line);
  until (Line = 'quit') or (Line = 'exit');

  // infinite loop with Break
  while True do
  begin
    WriteLn('Running...');
    if SomeCondition then Break;
  end;

  // Continue in while
  Count := 0;
  while Count < 10 do
  begin
    Inc(Count);
    if Count mod 2 = 0 then Continue;
    WriteLn(Count);   // 1 3 5 7 9
  end;
end;

With...Do & Goto

With...Do accède aux membres d'un enregistrement/objet sans répéter la variable — utile pour l'initialisation et la réduction de la verbosité. Évitez les With imbriqués (ambiguïté sur l'objet auquel appartient un membre). Goto saute à une étiquette — rarement utilisé en Delphi moderne (préférez Break/Continue/Exit) ; déclarez les étiquettes avec 'label'. Exit quitte la procédure immédiatement ; Exit(value) retourne une valeur depuis une fonction (syntaxe moderne). With peut rendre le code moins lisible s'il est surutilisé — utilisez-le avec parcimonie pour les cas simples.

delphi
type
  TPerson = record
    Name: string;
    Age: Integer;
    Email: string;
  end;

var
  P: TPerson;
  i: Integer;
label
  RetryPoint;   // declare a label for Goto
begin
  // With...Do: access record/object members without repeating the name
  with P do
  begin
    Name := 'Alice';
    Age := 30;
    Email := '[email protected]';
    WriteLn(Name, ' is ', Age);   // instead of P.Name, P.Age
  end;

  // With on a function result
  with TStringList.Create do
  try
    Add('line 1');
    Add('line 2');
    SaveToFile('output.txt');
  finally
    Free;
  end;

  // nested With (avoid — ambiguous)
  with P, TStringList.Create do
  try
    Add(Name);   // Name could be P.Name or TStringList.Name
  finally
    Free;
  end;

  // Goto (rarely used — prefer structured alternatives)
  i := 0;
RetryPoint:
  Inc(i);
  WriteLn('Attempt ', i);
  if i < 3 then
    Goto RetryPoint;
  WriteLn('Done after ', i, ' attempts');

  // Exit: leave the current procedure/function
  if P.Age < 0 then
  begin
    WriteLn('Invalid age');
    Exit;          // return immediately
  end;

  // Exit with a value (for functions)
  // Exit(42);  // returns 42 from the function
end;
03

Chaînes & Traitement de texte

Types de chaînes & Opérations

La chaîne par défaut de Delphi est UnicodeString (UTF-16, à comptage de références, copie sur écriture). Les chaînes sont indexées à 1 (S[1] est le premier caractère) — une source courante de bugs pour les programmeurs C. Length() retourne le nombre de caractères. Pos() trouve une sous-chaîne (retourne 0 si non trouvée, pas -1). Copy() extrait une sous-chaîne (Start, Count). StringReplace remplace (rfReplaceAll pour toutes les occurrences). Trim/TrimLeft/TrimRight suppriment les espaces. Split/Join sont des méthodes modernes (TArray<string>). Utilisez SameText pour une comparaison insensible à la casse.

delphi
var
  S: string;           // UnicodeString (default, UTF-16, reference-counted)
  A: AnsiString;       // 8-bit string (legacy, codepage-aware)
  W: WideString;       // COM-compatible (not reference-counted)
  SB: StringBuilder;   // mutable, for heavy concatenation
begin
  S := 'Hello, World';

  // length and indexing (1-indexed!)
  WriteLn(Length(S));        // 12
  WriteLn(S[1]);             // 'H' (first char — 1-indexed!)
  WriteLn(S[Length(S)]);     // 'd' (last char)

  // case conversion
  WriteLn(UpperCase(S));     // HELLO, WORLD
  WriteLn(LowerCase(S));     // hello, world

  // searching
  WriteLn(Pos('World', S));  // 8 (1-indexed position, 0 if not found)
  WriteLn(Pos('xyz', S));    // 0

  // substring
  WriteLn(Copy(S, 1, 5));    // 'Hello' (Start, Length)
  WriteLn(Copy(S, 8, 5));    // 'World'

  // modify (creates new string — strings are immutable-ish)
  S := StringReplace(S, 'World', 'Delphi', [rfReplaceAll]);
  WriteLn(S);                // Hello, Delphi

  // trim
  WriteLn(Trim('  hi  '));        // 'hi'
  WriteLn(TrimLeft('  hi  '));    // 'hi  '
  WriteLn(TrimRight('  hi  '));   // '  hi'

  // split
  var Parts: TArray<string>;
  Parts := 'a,b,c'.Split([',']);
  WriteLn(Length(Parts));    // 3

  // join
  WriteLn(string.Join('-', Parts));  // 'a-b-c'

  // comparison
  WriteLn('abc' = 'abc');    // TRUE (case-sensitive)
  WriteLn(AnsiCompareText('ABC', 'abc'));  // 0 (case-insensitive)
  WriteLn(SameText('ABC', 'abc'));         // TRUE
end;

Formatage de chaînes & Conversion

Format() est le sprintf de Delphi : %d (entier), %f (flottant), %s (chaîne), %x (hex), %m (monnaie), avec des modificateurs de largeur/précision. FloatToStrF donne un contrôle précis (ffFixed, ffCurrency, ffNumber, ffExponent). FormatDateTime formate les dates : yyyy (année à 4 chiffres), mm (mois), dd (jour), hh (heure), nn (minute), ss (seconde), dddd (nom complet du jour), mmmm (nom complet du mois). StrToInt/StrToFloat lèvent EConvertError sur une entrée invalide ; TryStrToInt retourne un booléen (plus sûr). Utilisez toujours Try... pour l'entrée utilisateur.

delphi
var
  N: Integer := 42;
  F: Double := 3.14159;
  S: string;
  D: TDateTime := Now;
begin
  // Format (like sprintf)
  S := Format('Integer: %d', [N]);              // 'Integer: 42'
  S := Format('Float: %f', [F]);                // 'Float: 3.14'
  S := Format('Float: %.4f', [F]);              // 'Float: 3.1416'
  S := Format('Hex: %x', [N]);                  // 'Hex: 2a'
  S := Format('String: %s', ['Hello']);         // 'String: Hello'
  S := Format('Padded: %10d', [N]);             // '        42'
  S := Format('Left: %-10d|', [N]);             // '42        |'
  S := Format('Multiple: %s=%d, %.2f', ['x', N, F]);

  // FloatToStrF (precise float formatting)
  S := FloatToStrF(F, ffFixed, 8, 2);           // '3.14'
  S := FloatToStrF(F, ffCurrency, 8, 2);        // '$3.14'
  S := FloatToStrF(1234567, ffNumber, 10, 0);   // '1,234,567'

  // date/time formatting
  S := FormatDateTime('yyyy-mm-dd', D);         // '2024-06-18'
  S := FormatDateTime('hh:nn:ss', D);           // '14:30:00'
  S := FormatDateTime('dddd, mmmm d, yyyy', D); // 'Tuesday, June 18, 2024'

  // string to number (throws on invalid)
  N := StrToInt('123');
  F := StrToFloat('3.14');
  D := StrToDateTime('2024-06-18');

  // safe parsing (TryStrTo...)
  if TryStrToInt('123', N) then
    WriteLn('Parsed: ', N);
  if TryStrToInt('abc', N) then
    WriteLn('Valid')
  else
    WriteLn('Invalid number');

  // IntToStr, FloatToStr
  WriteLn(IntToStr(42));
  WriteLn(FloatToStr(3.14));
end;

StringBuilder & TStringList

StringBuilder (mutable) est efficace pour les boucles qui construisent de grandes chaînes — Append modifie sur place au lieu de créer de nouvelles chaînes. TStringList est le couteau suisse de Delphi : une liste de chaînes qui peut trier, rechercher, contenir des paires clé=valeur (Values[]), charger/sauvegarder des fichiers (une ligne par élément) et diviser du texte délimité (CommaText, DelimitedText). TStringList est indexé à 0 (SL[0]) contrairement aux chaînes (S[1]). Enveloppez toujours dans try..finally pour Free. C'est la méthode la plus courante pour gérer les fichiers texte et les configurations simples en Delphi.

delphi
uses
  System.SysUtils, System.Classes;

var
  sb: StringBuilder;
  SL: TStringList;
  i: Integer;
begin
  // StringBuilder: efficient concatenation (mutable)
  sb := StringBuilder.Create;
  try
    for i := 1 to 1000 do
      sb.Append('Line ').Append(i).AppendLine;  // chainable
    WriteLn(sb.ToString);
  finally
    sb.Free;
  end;

  // TStringList: versatile string collection
  SL := TStringList.Create;
  try
    // add items
    SL.Add('apple');
    SL.Add('banana');
    SL.Add('cherry');
    WriteLn(SL.Count);          // 3
    WriteLn(SL[0]);             // 'apple' (0-indexed!)

    // sort and find
    SL.Sort;
    SL.Sorted := True;          // auto-sort on Add
    idx := SL.IndexOf('banana');  // find (returns -1 if not found)

    // comma-separated text
    SL.CommaText := 'red,green,blue';   // split into items
    WriteLn(SL.CommaText);               // 'blue,green,red'

    // key=value pairs
    SL.Clear;
    SL.Values['name'] := 'Alice';
    SL.Values['age'] := '30';
    WriteLn(SL.Values['name']);  // 'Alice'

    // file I/O (one line per item)
    SL.SaveToFile('items.txt');
    SL.LoadFromFile('items.txt');

    // delimited text
    SL.Delimiter := ';';
    SL.DelimitedText := 'a;b;c';
  finally
    SL.Free;
  end;
end;

Opérations sur les caractères & Encodage

Char est un caractère Unicode de 2 octets. Ord() obtient le point de code ; Char() convertit en retour. IsDigit/IsLetter/IsWhiteSpace/IsUpper/IsLower classent les caractères. ToUpper/ToLower convertissent la casse. TEncoding.UTF8.GetBytes convertit les chaînes en tableaux d'octets (essentiel pour les E/S fichier et le réseau) — UTF-8 utilise 1-4 octets par caractère. TEncoding.Unicode est UTF-16 LE (toujours 2 octets/caractère). Base64 (TNetEncoding.Base64) encode les données binaires en texte pour le transport. String et Char sont UTF-16 en interne ; convertissez en UTF-8 pour le stockage de fichiers et les protocoles réseau.

delphi
var
  C: Char;
  S: string;
  Bytes: TBytes;
  i: Integer;
begin
  C := 'A';
  WriteLn(Ord(C));              // 65 (ASCII/Unicode code point)
  WriteLn(Char(66));            // 'B' (code point to char)

  // char classification
  WriteLn(IsDigit('5'));        // TRUE
  WriteLn(IsLetter('A'));       // TRUE
  WriteLn(IsWhiteSpace(' '));   // TRUE
  WriteLn(IsUpper('A'));        // TRUE
  WriteLn(IsLower('a'));        // TRUE

  // case conversion
  WriteLn(ToUpper('a'));        // 'A'
  WriteLn(ToLower('A'));        // 'a'

  // iterate characters
  S := 'Hello';
  for C in S do
    Write(C, '(', Ord(C), ') ');
  WriteLn;   // H(72) e(101) l(108) l(108) o(111)

  // string <-> bytes (encoding)
  Bytes := TEncoding.UTF8.GetBytes('Hello');
  WriteLn(Length(Bytes));       // 5 (ASCII chars are 1 byte in UTF-8)
  Bytes := TEncoding.UTF8.GetBytes('héllo');
  WriteLn(Length(Bytes));       // 6 (é is 2 bytes in UTF-8)

  S := TEncoding.UTF8.GetString(Bytes);  // back to string

  // other encodings
  Bytes := TEncoding.ASCII.GetBytes('Hello');
  Bytes := TEncoding.Unicode.GetBytes('Hello');  // UTF-16 LE (2 bytes/char)

  // Base64 encoding (for binary in text)
  uses System.NetEncoding;
  var B64: string := TNetEncoding.Base64.EncodeBytesToString(Bytes);
  var Decoded: TBytes := TNetEncoding.Base64.DecodeStringToBytes(B64);

  // char arrays
  var Chars: array[0..4] of Char;
  Chars[0] := 'H'; Chars[1] := 'e'; Chars[2] := 'l'; Chars[3] := 'l'; Chars[4] := 'o';
  S := String(Chars);   // convert char array to string
end;

Expressions régulières

System.RegularExpressions fournit TRegex pour la correspondance de modèles. IsMatch teste ; Match trouve le premier ; Matches trouve tous. Les groupes capturent des parties avec des parenthèses — accédez via Groups[1], Groups[2] (indexé à 1). Replace substitue les correspondances ($1, $2 référencent les groupes). Split divise sur un modèle. Regex courants : \d (chiffre), \w (caractère de mot), \s (espace), + (un+), * (zéro+), {n} (exactement n), ^/$ (début/fin). roCompiled compile pour une utilisation répétée plus rapide. Validez toujours l'entrée utilisateur (emails, téléphones) avec regex.

delphi
uses
  System.RegularExpressions;

var
  Input, Pattern: string;
  Match: TMatch;
  Matches: TMatchCollection;
  Result: string;
begin
  Input := 'Phone: 123-456-7890, Zip: 10001';

  // check if matches
  if TRegEx.IsMatch(Input, 'd{3}-d{3}-d{4}') then
    WriteLn('Found a phone number');

  // find first match
  Match := TRegEx.Match(Input, 'd{5}');
  if Match.Success then
    WriteLn('Zip: ', Match.Value);   // '10001'

  // find all matches
  Matches := TRegEx.Matches(Input, 'd+');
  for Match in Matches do
    WriteLn(Match.Value);   // 123, 456, 7890, 10001

  // capture groups
  Match := TRegEx.Match('2024-06-18', '(d{4})-(d{2})-(d{2})');
  if Match.Success then
  begin
    WriteLn(Match.Groups[1].Value);  // '2024' (year)
    WriteLn(Match.Groups[2].Value);  // '06' (month)
    WriteLn(Match.Groups[3].Value);  // '18' (day)
  end;

  // replace
  Result := TRegEx.Replace(Input, 'd', 'X');
  // 'Phone: XXX-XXX-XXXX, Zip: XXXXX'

  // replace with match reference ($1, $2)
  Result := TRegEx.Replace('John Doe', '(w+) (w+)', '$2, $1');
  // 'Doe, John'

  // split
  var Parts: TArray<string>;
  Parts := TRegEx.Split('a,b;;c', '[,;]+');

  // common patterns
  Pattern := '^[w.-]+@[w.-]+.w+$';   // email
  Pattern := '^https?://[w./-]+$';       // URL
  Pattern := '^d{3}-d{3}-d{4}$';       // US phone

  // compiled regex (faster for repeated use)
  var Regex := TRegEx.Create('d+', [roCompiled]);
  try
    Match := Regex.Match(Input);
  finally
    Regex.Free;
  end;
end;
04

Tableaux, Enregistrements & Collections

Tableaux statiques & dynamiques

Les tableaux statiques ont une taille fixe définie à la compilation avec un intervalle d'index personnalisé (array[0..4] ou array[1..7]). Les tableaux dynamiques (array of T) sont redimensionnables avec SetLength — ils sont indexés à 0 et à comptage de références. High() retourne le dernier index ; Length() retourne le nombre. SetLength sur un tableau dynamique existant le redimensionne (en préservant les valeurs existantes si agrandissement). Définissez à nil pour libérer. Les littéraux de tableau dynamique utilisent [1, 2, 3]. Les tableaux dynamiques multidimensionnels sont des 'tableaux de tableaux' (jagged) — chaque ligne peut avoir une longueur différente.

delphi
var
  // static array (fixed size, compile-time)
  Nums: array[0..4] of Integer;        // 5 elements, indices 0..4
  Matrix: array[0..2, 0..2] of Double; // 3x3 2D array
  Days: array[1..7] of string;         // 1-indexed (custom range)

  // dynamic array (resizable at runtime)
  Dyn: array of Integer;
  Dyn2D: array of array of Integer;    // jagged 2D
  i, j: Integer;
begin
  // static array
  Nums[0] := 10;
  Nums[1] := 20;
  for i := 0 to High(Nums) do   // High(Nums) = 4
    WriteLn(Nums[i]);
  WriteLn(Length(Nums));         // 5

  // dynamic array
  SetLength(Dyn, 5);             // allocate 5 elements (0-indexed)
  Dyn[0] := 1;
  Dyn[1] := 2;
  for i := 0 to High(Dyn) do
    WriteLn(Dyn[i]);

  SetLength(Dyn, 10);            // resize (preserves existing values)
  WriteLn(Length(Dyn));          // 10

  Dyn := nil;                    // free memory

  // dynamic array literal (modern Delphi)
  Dyn := [1, 2, 3, 4, 5];
  WriteLn(Length(Dyn));          // 5

  // 2D dynamic array
  SetLength(Dyn2D, 3);           // 3 rows
  for i := 0 to 2 do
  begin
    SetLength(Dyn2D[i], 3);      // 3 cols per row
    for j := 0 to 2 do
      Dyn2D[i][j] := i * 3 + j;
  end;

  // array slice (Open Array)
  WriteLn(Length(Dyn));          // 5
end;

Enregistrements (Structs)

Les enregistrements sont des types valeur (copiés à l'affectation, alloués sur la pile) — comme les structs en C. Les enregistrements Delphi modernes peuvent avoir des méthodes, des propriétés et une visibilité (private/public). Les enregistrements n'ont pas besoin d'être libérés (pas d'allocation de tas). Les enregistrements variants (case...of) créent une union où les champs partagent la mémoire — utile pour les étiquettes de type. Utilisez les enregistrements pour les petites données légères (points, coordonnées, configuration). Utilisez les classes pour les plus grands objets nécessitant l'héritage ou le polymorphisme. Les enregistrements sont plus rapides (pas d'allocation de tas) mais ne peuvent pas être hérités.

delphi
type
  // simple record (like a struct)
  TPoint = record
    X, Y: Integer;
  end;

  // record with methods (modern Delphi)
  TPerson = record
    Name: string;
    Age: Integer;
    // methods
    function Greet: string;
    procedure Birthday;
    // properties
    property IsAdult: Boolean read GetIsAdult;
  private
    function GetIsAdult: Boolean;
  end;

  // variant record (union — fields share memory)
  TValue = record
    case IsInt: Boolean of
      True: (IntVal: Integer);
      False: (FloatVal: Double);
  end;

// implementing record methods
function TPerson.Greet: string;
begin
  Result := 'Hi, I am ' + Name;
end;

procedure TPerson.Birthday;
begin
  Inc(Age);
end;

function TPerson.GetIsAdult: Boolean;
begin
  Result := Age >= 18;
end;

var
  P: TPerson;
  Pt: TPoint;
  V: TValue;
begin
  // record assignment copies all fields (value type)
  P.Name := 'Alice';
  P.Age := 30;
  WriteLn(P.Greet);       // Hi, I am Alice
  P.Birthday;
  WriteLn(P.Age);         // 31
  WriteLn(P.IsAdult);     // TRUE

  // record constructors (modern Delphi)
  P := TPerson.Create;    // zero-initializes
  P.Name := 'Bob';

  // variant record
  V.IsInt := True;
  V.IntVal := 42;
  WriteLn(V.IntVal);      // 42
  V.IsInt := False;
  V.FloatVal := 3.14;
  WriteLn(V.FloatVal);    // 3.14
end;

Ensembles & Énumérations

Les ensembles sont la fonctionnalité unique de Delphi — une collection de valeurs d'une énumération ou d'un sous-intervalle (max 256 éléments). Opérateurs : + (union), - (différence), * (intersection), = (égalité), <= (sous-ensemble), in (appartenance). Include/Exclude sont des ajouts/suppressions d'élément unique efficaces. Les ensembles sont stockés comme des bitmaps (très rapides). Utilisations courantes : TFontStyles (fsBold, fsItalic), ensemble de Char pour la validation (['0'..'9']), jours de la semaine. Les énumérations sont des types ordinaux — itérez avec Low() à High(), convertissez en chaîne avec GetEnumName. Les ensembles rendent les combinaisons de drapeaux élégantes et de type sûr.

delphi
type
  // enumeration
  TDay = (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
  TColor = (Red, Green, Blue);

  // set type (collection of enum values)
  TDays = set of TDay;
  TColors = set of TColor;
  TChars = set of Char;     // set of characters

var
  Weekdays: TDays;
  MyColors: TColors;
  D: TDay;
  Digits: TChars;
  C: Char;
begin
  // set operations
  Weekdays := [Mon, Tue, Wed, Thu, Fri];
  MyColors := [Red, Blue];

  // add and remove
  Include(Weekdays, Sat);    // Weekdays := Weekdays + [Sat]
  Exclude(Weekdays, Sun);    // Weekdays := Weekdays - [Sun]

  // set operators
  Weekdays := Weekdays + [Sat];     // union
  Weekdays := Weekdays - [Sat];     // difference
  Weekend := [Sat, Sun];
  if Weekdays * Weekend = [] then   // intersection is empty
    WriteLn('No overlap');

  // membership test
  if Mon in Weekdays then
    WriteLn('Monday is a weekday');

  // iterate enum
  for D := Low(TDay) to High(TDay) do
    WriteLn(D);              // 0 1 2 3 4 5 6 (ord values)

  // convert enum to string
  WriteLn(GetEnumName(TypeInfo(TDay), Ord(Mon)));  // 'Mon'

  // set of Char (common for validation)
  Digits := ['0'..'9'];
  C := '5';
  if C in Digits then
    WriteLn('Is a digit');

  // set comparison
  if MyColors = [Red, Blue] then
    WriteLn('Equal sets');
  if [Red] <= MyColors then     // subset
    WriteLn('Red is included');
end;

TList, TDictionary & Génériques

System.Generics.Collections fournit des collections de type sûr : TList<T> (tableau dynamique), TDictionary<K,V> (table de hachage), TQueue<T> (FIFO), TStack<T> (LIFO), THashSet<T> (éléments uniques). Toutes sont génériques (vérification de type à la compilation, pas de transtypage). TList a Add/Remove/Delete/Sort/Contains/IndexOf. TDictionary a Add/Remove/TryGetValue/Keys/Values. TObjectList<T> possède ses objets (les libère automatiquement) — utilisez-le lorsque la liste doit gérer les durées de vie des objets. Enveloppez toujours dans try..finally pour Free (ce sont des objets, pas des enregistrements).

delphi
uses
  System.Generics.Collections;

var
  Nums: TList<Integer>;
  Ages: TDictionary<string, Integer>;
  Unique: THashSet<string>;
  Queue: TQueue<string>;
  Stack: TStack<Integer>;
  i: Integer;
  K: string;
  V: Integer;
begin
  // TList<T>: dynamic array (generic)
  Nums := TList<Integer>.Create;
  try
    Nums.Add(1);
    Nums.Add(2);
    Nums.AddRange([3, 4, 5]);
    WriteLn(Nums.Count);          // 5
    WriteLn(Nums[0]);             // 1 (0-indexed)
    Nums[0] := 100;
    Nums.Remove(2);               // by value
    Nums.Delete(0);               // by index
    Nums.Sort;                    // sort in place
    Nums.Reverse;
    if Nums.Contains(3) then
      WriteLn('Found');
    for i in Nums do
      WriteLn(i);
  finally
    Nums.Free;
  end;

  // TDictionary<TKey, TValue>: hash map
  Ages := TDictionary<string, Integer>.Create;
  try
    Ages.Add('Alice', 30);
    Ages.Add('Bob', 25);
    Ages['Eve'] := 28;            // add or update
    if Ages.TryGetValue('Alice', V) then
      WriteLn('Alice is ', V);    // 30
    for K in Ages.Keys do
      WriteLn(K);
    for V in Ages.Values do
      WriteLn(V);
    Ages.Remove('Bob');
  finally
    Ages.Free;
  end;

  // TQueue<T> (FIFO) and TStack<T> (LIFO)
  Queue := TQueue<string>.Create;
  try
    Queue.Enqueue('first');
    Queue.Enqueue('second');
    WriteLn(Queue.Dequeue);       // 'first'
  finally
    Queue.Free;
  end;

  // TObjectList<T> (owns its objects — frees them on Clear/Free)
  // uses System.Generics.Collections;
  // var People: TObjectList<TPerson>;
  // People := TObjectList<TPerson>.Create;
  // People.Add(TPerson.Create);  // freed when People is freed
end;

Algorithmes de tableaux & Tri

TArray est une classe utilitaire pour les opérations sur tableaux : Sort (avec IComparer personnalisé optionnel), BinarySearch (recherche rapide sur tableaux triés), Reverse, Copy. TComparer<T>.Construct crée une fonction de comparaison en ligne (méthode anonyme). Le tri par un champ nécessite un comparateur personnalisé. BinarySearch retourne un booléen et l'index trouvé — le tableau DOIT être trié au préalable. Pour les recherches complexes, une boucle linéaire avec Break est simple et claire. TArray.Sort est un tri rapide (O(n log n) en moyenne).

delphi
uses
  System.Generics.Collections, System.Generics.Defaults;

var
  Nums: TArray<Integer>;
  People: TArray<TPerson>;
  i: Integer;
begin
  // sort an array
  Nums := TArray<Integer>.Create(5, 3, 1, 4, 2);
  TArray.Sort<Integer>(Nums);
  // Nums = [1, 2, 3, 4, 5]

  // sort descending (custom comparer)
  TArray.Sort<Integer>(Nums, TComparer<Integer>.Construct(
    function(const L, R: Integer): Integer
    begin
      Result := R - L;   // reverse comparison
    end));

  // binary search (array must be sorted)
  TArray.Sort<Integer>(Nums);
  var Found: Boolean := TArray.BinarySearch<Integer>(Nums, 3, i);
  if Found then
    WriteLn('Found at index ', i);

  // sort array of records by a field
  type TPerson = record Name: string; Age: Integer; end;
  SetLength(People, 3);
  People[0] := TPerson.Create('Alice', 30);
  People[1] := TPerson.Create('Bob', 25);
  People[2] := TPerson.Create('Carol', 28);

  TArray.Sort<TPerson>(People, TComparer<TPerson>.Construct(
    function(const L, R: TPerson): Integer
    begin
      Result := L.Age - R.Age;   // sort by age
    end));

  for i := 0 to High(People) do
    WriteLn(People[i].Name, ': ', People[i].Age);

  // reverse an array
  TArray.Reverse<Integer>(Nums);

  // copy an array
  var Copy: TArray<Integer>;
  Copy := Copy(Nums, 0, Length(Nums));

  // find with a predicate
  var FoundIdx: Integer := -1;
  for i := 0 to High(People) do
    if People[i].Age > 28 then
    begin
      FoundIdx := i;
      Break;
    end;
end;
05

Procédures, Fonctions & Paramètres

Procédures & Fonctions

Les procédures (pas de valeur de retour) et les fonctions (retournent une valeur) sont les sous-routines de Delphi. La variable Result est la valeur de retour — affectez-lui (la fonction retourne lorsqu'elle se termine). Exit() retourne immédiatement avec une valeur (syntaxe moderne). Les déclarations forward vous permettent d'appeler une fonction avant que son corps ne soit défini (utile pour la récursivité mutuelle). Les fonctions peuvent retourner n'importe quel type, y compris des enregistrements, des tableaux et des objets. Exit sans valeur quitte simplement la procédure. La variable Result est implicitement déclarée et correspond au type de retour.

delphi
// Procedure: performs an action (no return value)
procedure Greet(Name: string);
begin
  WriteLn('Hello, ', Name, '!');
end;

// Function: returns a value
function Add(A, B: Integer): Integer;
begin
  Result := A + B;          // Result is the return variable
  // Exit(42);              // alternative: return immediately with 42
end;

// Function with multiple return paths
function Classify(Score: Integer): string;
begin
  if Score >= 90 then
    Exit('A');              // return immediately
  if Score >= 80 then
    Exit('B');
  Result := 'F';            // default return
end;

// Function returning a record
function MakePoint(X, Y: Integer): TPoint;
begin
  Result.X := X;
  Result.Y := Y;
end;

// forward declaration (use before full definition)
function Calc(X: Integer): Integer; forward;

procedure Demo;
var
  Sum: Integer;
  P: TPoint;
begin
  Greet('Alice');               // procedure call
  Sum := Add(3, 4);             // function call
  WriteLn(Sum);                 // 7
  WriteLn(Classify(85));        // B
  P := MakePoint(3, 4);
  WriteLn(Calc(10));
end;

function Calc(X: Integer): Integer;
begin
  Result := X * 2;
end;

Paramètres : Const, Var, Out, Default

const : paramètre en lecture seule (évite aussi de copier les chaînes/tableaux — efficace). var : passage par référence (modifie la variable de l'appelant — comme ref en C#). out : sortie uniquement (l'appelant n'initialise pas ; la fonction la définit). Les paramètres par défaut doivent venir en dernier. Les paramètres de tableau ouvert (array of T) acceptent n'importe quel tableau ou un littéral [1,2,3] — utilisez const pour l'efficacité. const est préféré pour les chaînes et les tableaux (pas de copie) ; utilisez var uniquement lorsque vous devez modifier la valeur de l'appelant. Les tableaux ouverts sont indexés à 0 indépendamment des bornes du tableau source.

delphi
// const: can't be modified (also efficient for strings/arrays)
procedure Show(const S: string);
begin
  WriteLn(S);
  // S := 'new';  // ERROR: can't modify const
end;

// var: pass by reference (can modify caller's variable)
procedure Swap(var A, B: Integer);
var
  Temp: Integer;
begin
  Temp := A;
  A := B;
  B := Temp;
end;

// out: output-only parameter (caller doesn't need to initialize)
procedure GetValues(out X, Y: Integer);
begin
  X := 10;
  Y := 20;
end;

// default parameters (must be at the end)
function Power(Base: Double; Exp: Integer = 2): Double;
begin
  Result := Power(Base, Exp);   // Math.Power
end;

// open array parameter (accepts any array)
function Sum(const Values: array of Integer): Integer;
var
  i: Integer;
begin
  Result := 0;
  for i := 0 to High(Values) do
    Result := Result + Values[i];
end;

// 'const' for open arrays (efficient — no copy)
procedure ShowAll(const Items: array of string);
var
  S: string;
begin
  for S in Items do
    WriteLn(S);
end;

var
  X, Y: Integer;
  Nums: array[0..4] of Integer;
begin
  Swap(X, Y);              // var: modifies X and Y
  GetValues(X, Y);         // out: sets X and Y
  WriteLn(Power(3));       // 9 (Exp defaults to 2)
  WriteLn(Power(2, 10));   // 1024
  WriteLn(Sum([1, 2, 3, 4, 5]));   // 15 (open array literal)
  ShowAll(['a', 'b', 'c']);
end;

Surcharge & Paramètres par défaut

La surcharge permet à plusieurs routines de partager un nom avec différentes listes de paramètres — le compilateur choisit la meilleure correspondance. La directive 'overload' est requise. La surcharge est plus propre que d'inventer différents noms (AddInt, AddDouble). Les paramètres par défaut sont une alternative — les appelants peuvent les omettre. Préférez la surcharge lorsque la logique diffère par type ; utilisez les valeurs par défaut pour les valeurs optionnelles. L'ambiguïté (deux surcharges qui correspondent également) est une erreur de compilation. Les surcharges doivent différer par le nombre ou les types de paramètres (le type de retour seul ne suffit pas).

delphi
// overloading: same name, different parameters
function Add(A, B: Integer): Integer; overload;
begin
  Result := A + B;
end;

function Add(A, B: Double): Double; overload;
begin
  Result := A + B;
end;

function Add(A, B, C: Integer): Integer; overload;
begin
  Result := A + B + C;
end;

function Add(const Values: array of Integer): Integer; overload;
var
  i: Integer;
begin
  Result := 0;
  for i := 0 to High(Values) do
    Result := Result + Values[i];
end;

// default parameters (alternative to some overloads)
function CreateRect(Left, Top: Integer; Width: Integer = 100;
  Height: Integer = 50): TRect;
begin
  Result := Rect(Left, Top, Left + Width, Top + Height);
end;

var
  R: TRect;
begin
  WriteLn(Add(1, 2));           // 3 (Integer overload)
  WriteLn(Add(1.5, 2.5));       // 4.0 (Double overload)
  WriteLn(Add(1, 2, 3));        // 6 (3-arg overload)
  WriteLn(Add([1, 2, 3, 4]));   // 10 (array overload)

  R := CreateRect(10, 20);              // uses defaults: 100x50
  R := CreateRect(10, 20, 200);         // Width=200, Height=50
  R := CreateRect(10, 20, 200, 100);    // all specified
end;

Méthodes anonymes & Fermetures

Les méthodes anonymes (fermetures) sont des fonctions/procédures en ligne affectées à des types 'reference to'. Elles capturent les variables de leur portée englobante (fermetures). 'reference to function'/'reference to procedure' sont les types délégués. Les méthodes anonymes permettent la programmation fonctionnelle : fonctions d'ordre supérieur (Apply prend une fonction), fermetures (MakeMultiplier retourne une fonction qui se souvient de Factor) et comparateurs personnalisés (TComparer<T>.Construct). Elles sont essentielles pour le tri générique, les gestionnaires d'événements et les rappels. Les variables capturées sont allouées sur le tas (elles survivent à la fonction englobante).

delphi
type
  TMathFunc = reference to function(X: Integer): Integer;
  TNotifyProc = reference to procedure(Msg: string);

// function that takes a function
function Apply(Func: TMathFunc; Values: array of Integer): Integer;
var
  i: Integer;
begin
  Result := 0;
  for i := 0 to High(Values) do
    Result := Result + Func(Values[i]);
end;

// function that returns a function (closure)
function MakeMultiplier(Factor: Integer): TMathFunc;
begin
  Result := function(X: Integer): Integer
           begin
             Result := X * Factor;   // captures Factor
           end;
end;

var
  Double: TMathFunc;
  Triple: TMathFunc;
begin
  // anonymous method (inline function)
  Double := function(X: Integer): Integer
            begin
              Result := X * 2;
            end;

  WriteLn(Double(21));    // 42

  // use with higher-order functions
  WriteLn(Apply(Double, [1, 2, 3, 4]));   // 20 (2+4+6+8)

  // closure: captures the Factor variable
  Triple := MakeMultiplier(3);
  WriteLn(Triple(5));     // 15

  // anonymous procedure
  var Log: TNotifyProc := procedure(Msg: string)
    begin
      WriteLn('[LOG] ', Msg);
    end;
  Log('Hello');

  // use with TList.Sort (custom comparison)
  var Nums: TList<Integer>;
  Nums := TList<Integer>.Create;
  try
    Nums.AddRange([5, 3, 1, 4, 2]);
    Nums.Sort(TComparer<Integer>.Construct(
      function(const L, R: Integer): Integer
      begin
        Result := L - R;
      end));
  finally
    Nums.Free;
  end;
end;

Récursivité & Routines d'assistance

La récursivité est une fonction qui s'appelle elle-même — a besoin d'un cas de base pour terminer. Factorielle et Fibonacci sont des exemples classiques. La récursivité terminale (où l'appel récursif est la dernière opération) peut être optimisée par le compilateur. Les procédures/fonctions imbriquées sont déclarées à l'intérieur d'une autre routine et peuvent accéder à ses variables (portée lexicale) — utiles pour les assistants qui n'ont pas besoin d'être visibles à l'extérieur. Attention au débordement de pile avec une récursivité profonde (utilisez l'itération pour les grandes entrées). La mémoïsation (mise en cache des résultats) peut accélérer les algorithmes récursifs comme Fibonacci.

delphi
// classic recursion
function Factorial(N: Integer): Integer;
begin
  if N <= 1 then
    Result := 1
  else
    Result := N * Factorial(N - 1);
end;

// tail recursion (compiler may optimize)
function SumRange(N: Integer; Acc: Integer = 0): Integer;
begin
  if N = 0 then
    Result := Acc
  else
    Result := SumRange(N - 1, Acc + N);
end;

// Fibonacci (naive — exponential time)
function Fib(N: Integer): Integer;
begin
  if N < 2 then
    Result := N
  else
    Result := Fib(N - 1) + Fib(N - 2);
end;

// nested procedure (helper with access to outer variables)
procedure ProcessData(Data: array of Integer);
var
  Total: Integer;

  procedure SumAll;     // nested, sees Total and Data
  var
    i: Integer;
  begin
    Total := 0;
    for i := 0 to High(Data) do
      Total := Total + Data[i];
  end;

  function Average: Double;   // nested function
  begin
    if Length(Data) = 0 then
      Result := 0
    else
      Result := Total / Length(Data);
  end;

begin
  SumAll;               // calls nested procedure
  WriteLn('Sum: ', Total);
  WriteLn('Avg: ', Average:0:2);
end;

begin
  WriteLn(Factorial(5));     // 120
  WriteLn(SumRange(10));     // 55
  WriteLn(Fib(10));          // 55
  ProcessData([1, 2, 3, 4, 5]);
end;
06

Classes & POO

Définition de classe, Constructeur & Destructeur

Les classes sont des types référence (allouées sur le tas, accédées via des pointeurs). Create est le constructeur ; Destroy est le destructeur (toujours surcharger ; appelé par Free). 'inherited' appelle la méthode de la classe de base. Les champs utilisent le préfixe F par convention. Les propriétés (property X: Type read GetX write SetX) fournissent un accès contrôlé — les appelants utilisent P.Age mais le setter valide. Visibilité : private (unité uniquement dans les anciens Delphi ; strict private est vraiment privé), protected (sous-classes), public (tout le monde), published (RTTI, pour les formulaires/inspecteurs). Enveloppez toujours la création d'objet dans try..finally pour garantir que Free est appelé.

delphi
type
  TPerson = class
  private
    FName: string;          // private field (convention: F prefix)
    FAge: Integer;
    procedure SetAge(Value: Integer);   // setter for validation
  protected
    // visible to subclasses
    function GetDescription: string; virtual;
  public
    constructor Create(Name: string; Age: Integer);   // constructor
    destructor Destroy; override;                      // destructor
    // properties (with getters/setters)
    property Name: string read FName;                  // read-only
    property Age: Integer read FAge write SetAge;      // validated
    property Description: string read GetDescription;
    // method
    function Greet: string; virtual;
  end;

constructor TPerson.Create(Name: string; Age: Integer);
begin
  inherited Create;         // call base constructor (TObject.Create)
  FName := Name;
  FAge := Age;
end;

destructor TPerson.Destroy;
begin
  // free owned objects here
  inherited;                // call base destructor
end;

procedure TPerson.SetAge(Value: Integer);
begin
  if (Value < 0) or (Value > 150) then
    raise ERangeError.Create('Invalid age');
  FAge := Value;
end;

function TPerson.GetDescription: string;
begin
  Result := Format('%s (%d)', [FName, FAge]);
end;

function TPerson.Greet: string;
begin
  Result := 'Hi, I am ' + FName;
end;

var
  P: TPerson;
begin
  P := TPerson.Create('Alice', 30);
  try
    WriteLn(P.Greet);          // Hi, I am Alice
    WriteLn(P.Description);    // Alice (30)
    P.Age := 31;               // uses setter
    // P.Age := 200;           // raises ERangeError
  finally
    P.Free;                    // calls destructor
  end;
end;

Propriétés & Propriétés indexées

Les propriétés encapsulent l'accès aux champs avec des getters/setters. Les propriétés en lecture seule ont uniquement un spécificateur 'read'. La directive 'default' fait d'une propriété indexée la propriété par défaut — ainsi L[i] fonctionne au lieu de L.Items[i]. Les propriétés peuvent avoir un accès direct au champ (read FCount) ou un accès par méthode (read GetItem write SetItem) pour la validation/calcul. Les propriétés indexées permettent une syntaxe de type tableau. Les propriétés published (section published) sont visibles par RTTI et le concepteur de formulaires. Les propriétés sont la façon de Delphi d'exposer les données en toute sécurité — préférez-les toujours aux champs publics.

delphi
type
  TList = class
  private
    FItems: array of Integer;
    FCount: Integer;
    function GetItem(Index: Integer): Integer;
    procedure SetItem(Index: Integer; Value: Integer);
  public
    constructor Create;
    destructor Destroy; override;
    procedure Add(Value: Integer);
    // default array property (enables List[i] syntax)
    property Items[Index: Integer]: Integer read GetItem write SetItem; default;
    property Count: Integer read FCount;
  end;

constructor TList.Create;
begin
  inherited Create;
  FCount := 0;
end;

destructor TList.Destroy;
begin
  SetLength(FItems, 0);
  inherited;
end;

function TList.GetItem(Index: Integer): Integer;
begin
  if (Index < 0) or (Index >= FCount) then
    raise ERangeError.Create('Index out of range');
  Result := FItems[Index];
end;

procedure TList.SetItem(Index: Integer; Value: Integer);
begin
  if (Index < 0) or (Index >= FCount) then
    raise ERangeError.Create('Index out of range');
  FItems[Index] := Value;
end;

procedure TList.Add(Value: Integer);
begin
  Inc(FCount);
  SetLength(FItems, FCount);
  FItems[FCount - 1] := Value;
end;

var
  L: TList;
begin
  L := TList.Create;
  try
    L.Add(10);
    L.Add(20);
    WriteLn(L[0]);    // 10 (default property — no need for L.Items[0])
    L[1] := 99;       // uses setter
    WriteLn(L.Count); // 2
  finally
    L.Free;
  end;
end;

Héritage & Polymorphisme

Héritage : TDog = class(TAnimal) signifie que TDog hérite de TAnimal. 'virtual' marque une méthode pour le polymorphisme ; 'override' la remplace dans une sous-classe. À l'exécution, la méthode de l'objet RÉEL s'exécute (dispatch virtuel) — appeler Speak sur une référence TAnimal qui contient un TDog appelle TDog.Speak. Les méthodes statiques (Move) sont déterminées par le type de la variable, pas de l'objet. 'inherited' appelle la méthode de base. Les constructeurs peuvent être virtuels (modèle de fabrique). Utilisez virtual/override pour le polymorphisme ; les méthodes statiques lorsque le comportement est fixe. Libérez toujours les objets que vous créez.

delphi
type
  TAnimal = class
  public
    constructor Create; virtual;       // virtual constructor (factory pattern)
    function Speak: string; virtual;   // virtual: can be overridden
    function Move: string;             // static: can't be overridden
  end;

  TDog = class(TAnimal)
  public
    constructor Create; override;
    function Speak: string; override;  // override the virtual method
  end;

  TCat = class(TAnimal)
  public
    function Speak: string; override;
  end;

constructor TAnimal.Create;
begin
  inherited;
end;

function TAnimal.Speak: string;
begin
  Result := '...';
end;

function TAnimal.Move: string;
begin
  Result := 'Moving';
end;

constructor TDog.Create;
begin
  inherited Create;    // call TAnimal.Create
  WriteLn('Dog created');
end;

function TDog.Speak: string;
begin
  Result := 'Woof';
end;

function TCat.Speak: string;
begin
  Result := 'Meow';
end;

// polymorphism: array of base class, different behaviors
var
  Animals: array of TAnimal;
  i: Integer;
begin
  SetLength(Animals, 3);
  Animals[0] := TDog.Create;
  Animals[1] := TCat.Create;
  Animals[2] := TAnimal.Create;

  for i := 0 to High(Animals) do
  begin
    WriteLn(Animals[i].Speak);   // Woof, Meow, ... (virtual dispatch)
    WriteLn(Animals[i].Move);    // Moving, Moving, Moving (static)
  end;

  for i := 0 to High(Animals) do
    Animals[i].Free;
end;

Méthodes abstraites & Méthodes de classe

Les classes abstraites (class abstract) ne peuvent pas être instanciées — elles définissent un contrat pour les sous-classes. Les méthodes abstraites (virtual; abstract) n'ont pas d'implémentation — les sous-classes DOIVENT les surcharger. Cela impose que chaque forme fournisse Area/Perimeter. Les méthodes de classe (class function/procedure) n'ont pas besoin d'instance — appelez via TShape.ShapeCount. Les variables de classe (class var) sont partagées entre toutes les instances. Le modèle de Méthode Modèle : TShape.Describe appelle Area/Perimeter abstraits, qui sont remplis par les sous-classes. Les méthodes abstraites définissent 'quoi' ; les sous-classes définissent 'comment'.

delphi
type
  TShape = class abstract        // can't be instantiated directly
  public
    function Area: Double; virtual; abstract;   // must be overridden
    function Perimeter: Double; virtual; abstract;
    procedure Describe; virtual;
    // class method (no instance needed)
    class function ShapeCount: Integer; static;
    class var FCount: Integer;    // class variable (shared)
  end;

  TCircle = class(TShape)
  private
    FRadius: Double;
  public
    constructor Create(Radius: Double);
    function Area: Double; override;
    function Perimeter: Double; override;
  end;

  TRectangle = class(TShape)
  private
    FWidth, FHeight: Double;
  public
    constructor Create(W, H: Double);
    function Area: Double; override;
    function Perimeter: Double; override;
  end;

class function TShape.ShapeCount: Integer;
begin
  Result := FCount;
end;

procedure TShape.Describe;
begin
  WriteLn(Format('Area: %.2f, Perimeter: %.2f', [Area, Perimeter]));
end;

constructor TCircle.Create(Radius: Double);
begin
  inherited Create;
  FRadius := Radius;
  Inc(FCount);
end;

function TCircle.Area: Double;
begin
  Result := Pi * FRadius * FRadius;
end;

function TCircle.Perimeter: Double;
begin
  Result := 2 * Pi * FRadius;
end;

var
  S: TShape;
begin
  // TShape.Create;  // ERROR: abstract class can't be instantiated
  S := TCircle.Create(5);
  try
    S.Describe;          // Area: 78.54, Perimeter: 31.42
    WriteLn(TShape.ShapeCount);  // class method (no instance)
  finally
    S.Free;
  end;
end;

Interfaces & Héritage multiple

Les interfaces sont des contrats purs (pas de champs, pas d'implémentation) — la façon de Delphi d'atteindre l'héritage multiple de type. Une classe peut implémenter plusieurs interfaces (TButton implémente IComparable, IDrawable, IDisposable). Les interfaces peuvent avoir des GUID pour les casts QueryInterface/as. TInterfacedObject fournit le comptage de références — lorsque la dernière référence d'interface sort de la portée, l'objet est libéré automatiquement (n'appelez pas Free !). Utilisez les interfaces pour le découplage : le code dépend d'IDrawable, pas de TButton. L'opérateur 'as' convertit vers une interface (lève une exception si non pris en charge). Les interfaces sont l'épine dorsale du support COM de Delphi et des architectures de plugins modernes.

delphi
type
  // interface: pure contract (no implementation, no fields)
  IComparable = interface
    function CompareTo(Other: TObject): Integer;
  end;

  IDrawable = interface
    procedure Draw;
  end;

  // interfaces have GUIDs (for QueryInterface / as operator)
  IDisposable = interface
    ['{12345678-1234-1234-1234-123456789012}']
    procedure Dispose;
  end;

  // class implementing multiple interfaces
  TButton = class(TInterfacedObject, IComparable, IDrawable, IDisposable)
  private
    FLabel: string;
  public
    constructor Create(ALabel: string);
    function CompareTo(Other: TObject): Integer;
    procedure Draw;
    procedure Dispose;
  end;

constructor TButton.Create(ALabel: string);
begin
  FLabel := ALabel;
end;

function TButton.CompareTo(Other: TObject): Integer;
begin
  Result := CompareText(FLabel, (Other as TButton).FLabel);
end;

procedure TButton.Draw;
begin
  WriteLn('Drawing button: ', FLabel);
end;

procedure TButton.Dispose;
begin
  WriteLn('Disposing ', FLabel);
end;

var
  Btn: TButton;
  Drawable: IDrawable;
  Comp: IComparable;
begin
  Btn := TButton.Create('OK');
  Btn.Draw;

  // assign to interface variable (reference counting!)
  Drawable := Btn as IDrawable;
  Drawable.Draw;

  Comp := Btn;
  WriteLn(Comp.CompareTo(Btn));   // 0

  // TInterfacedObject uses reference counting
  // when the last interface reference is released, the object is freed
  // (don't call Free on interface-referenced objects!)
end;
07

Exceptions & Gestion d'erreurs

Try...Except...Finally

try...except attrape les exceptions (comme try/catch en C#). Chaque 'on E: ExceptionType do' gère une exception spécifique. try...finally garantit que le nettoyage s'exécute quelles que soient les exceptions (pas de gestion d'exception — utilisez-le pour les appels Free). Le modèle est try...try...except...finally (except interne pour la gestion, finally externe pour le nettoyage). 'raise' (seul) relance l'exception courante. Exception est la classe de base ; EFileNotFoundException, EInOutError sont des sous-classes. Mettez toujours l'exception la plus spécifique en premier et Exception (base) en dernier. Ne laissez jamais un except vide (avale silencieusement les erreurs).

delphi
var
  F: TextFile;
  S: string;
begin
  // try...except: catch exceptions
  try
    AssignFile(F, 'nonexistent.txt');
    Reset(F);
    ReadLn(F, S);
    CloseFile(F);
  except
    on E: EFileNotFoundException do
      WriteLn('File not found: ', E.Message);
    on E: EInOutError do
      WriteLn('I/O error: ', E.Message);
    on E: Exception do   // catch-all (must be last)
      WriteLn('Unexpected: ', E.ClassName, ': ', E.Message);
  end;

  // try...finally: cleanup (always runs, even on exception)
  var SL: TStringList;
  SL := TStringList.Create;
  try
    SL.LoadFromFile('data.txt');
    WriteLn(SL.Text);
  finally
    SL.Free;   // ALWAYS runs, even if an exception occurred
  end;

  // combined: try...try...except...finally
  SL := TStringList.Create;
  try
    try
      SL.LoadFromFile('data.txt');
    except
      on E: Exception do
      begin
        WriteLn('Error loading: ', E.Message);
        SL.Clear;   // fallback
      end;
    end;
    WriteLn(SL.Text);
  finally
    SL.Free;
  end;

  // re-raise
  try
    RiskyOperation;
  except
    on E: Exception do
    begin
      WriteLn('Logging: ', E.Message);
      raise;   // re-raise the same exception
    end;
  end;
end;

Lever & Exceptions personnalisées

Raise crée une exception : raise ExceptionType.Create('message'). CreateFmt est comme Format + Create. Les exceptions personnalisées héritent de Exception (ou d'une sous-classe spécifique) et peuvent transporter des données supplémentaires (TransactionId). Lors de l'enveloppement, préservez l'originale via SetInner ou un paramètre de constructeur. Les exceptions personnalisées permettent aux appelants d'attraper des types d'erreur spécifiques : attrapez ETransactionError séparément d'ERangeError. Incluez toujours un message significatif. Intégrées courantes : ERangeError, EDivByZero, EConvertError, EFileNotFoundException, EAccessViolation, EListError.

delphi
uses
  System.SysUtils;

// raise built-in exceptions
procedure CheckAge(Age: Integer);
begin
  if Age < 0 then
    raise ERangeError.CreateFmt('Age cannot be negative: %d', [Age]);
  if Age > 150 then
    raise ERangeError.Create('Age unrealistic');
end;

// raise with inner exception (wrapping)
function LoadConfig(Path: string): string;
begin
  try
    Result := TFile.ReadAllText(Path);
  except
    on E: Exception do
      raise EConfigError.Create('Config load failed').SetInner(E);
  end;
end;

// custom exception class
type
  ETransactionError = class(Exception)
  private
    FTransactionId: string;
  public
    constructor Create(const Msg, TxnId: string);
    property TransactionId: string read FTransactionId;
  end;

constructor ETransactionError.Create(const Msg, TxnId: string);
begin
  inherited Create(Msg);
  FTransactionId := TxnId;
end;

// using the custom exception
procedure ProcessPayment(Amount: Double; TxnId: string);
begin
  if Amount <= 0 then
    raise ETransactionError.Create('Amount must be positive', TxnId);
  // ... process
end;

var
  E: Exception;
begin
  try
    CheckAge(-5);
  except
    on E: ERangeError do
      WriteLn('Range error: ', E.Message);
  end;

  try
    ProcessPayment(-100, 'TXN-001');
  except
    on E: ETransactionError do
      WriteLn('Transaction ', E.TransactionId, ' failed: ', E.Message);
  end;
end;

Assertions & Débogage

Assert vérifie une condition et lève EAssertionFailed si fausse — utilisez pour les invariants (conditions qui doivent toujours être vraies). Les assertions sont désactivées avec {$C-} (ou supprimées dans les builds release) — ne les utilisez pas pour la validation d'entrée (utilisez des exceptions). OutputDebugString journalise vers le Journal d'événements de l'IDE (pas d'E/S fichier). TStopwatch mesure le temps écoulé avec précision. Exception.StackTrace nécessite des infos de débogage (fichier .map ou JCLDebug/FastMM). {$IFDEF DEBUG} active le code de débogage uniquement. Utilisez les assertions pour les erreurs de logique interne et les exceptions pour les erreurs utilisateur/externes.

delphi
uses
  System.SysUtils, System.Diagnostics;

var
  Age: Integer;
  SW: TStopwatch;
begin
  // Assert: checks a condition (only in {$C+} / debug builds)
  Age := 30;
  Assert(Age >= 0, 'Age should be non-negative');
  // Assert(Age < 0, 'This will raise EAssertionFailed');

  // {$C+} / {$C-}: enable/disable assertions
  {$C-}   // disable assertions (release builds)
  Assert(False, 'This won''t fire');
  {$C+}   // re-enable

  // OutputDebugString (visible in IDE debugger)
  OutputDebugString('Processing started');

  // TStopwatch for timing
  SW := TStopwatch.StartNew;
  Sleep(100);
  SW.Stop;
  WriteLn(Format('Elapsed: %d ms', [SW.ElapsedMilliseconds]));

  // raise with stack trace (uses System.DebugUtils / JCLDebug)
  try
    raise Exception.Create('Test error');
  except
    on E: Exception do
    begin
      WriteLn(E.Message);
      WriteLn(E.StackTrace);   // needs debug info / map file
    end;
  end;

  // conditional compilation
  {$IFDEF DEBUG}
  WriteLn('Debug build');
  {$ELSE}
  WriteLn('Release build');
  {$ENDIF}

  // Trace (simple logging)
  {$IFDEF DEBUG}
  WriteLn('[TRACE] Entering ProcessData');
  {$ENDIF}
end;

Modèles de gestion des exceptions

Modèles d'exception courants : (1) Boucles de réessai — enveloppez une opération sujette aux échecs dans un try/except à l'intérieur d'une boucle while, en relançant après MaxRetries. (2) Valeurs de repli — attrapez une exception spécifique (EConvertError) et retournez une valeur par défaut ; n'avalez que les exceptions que vous attendez vraiment. (3) Protection des ressources — enveloppez toujours Create/Free dans try/finally afin que les objets soient libérés même en cas d'exception (c'est l'idiome Delphi le plus important). (4) Ressources multiples — imbriquez les blocs try/finally ; acquérez chaque ressource dans son propre bloc protégé. (5) Validation — levez des types d'exception spécifiques (EArgumentException, ERangeError) tôt avec des messages descriptifs. N'attrapez jamais Exception et ne continuez silencieusement — au minimum, journalisez-la. Préférez try/finally pour le nettoyage et try/except pour une récupération véritable.

delphi
uses
  System.SysUtils, System.Classes;

// Pattern 1: Retry with backoff
function DownloadWithRetry(const URL: string; MaxRetries: Integer): string;
var
  Attempt: Integer;
  Done: Boolean;
begin
  Attempt := 0;
  Done := False;
  while (not Done) and (Attempt < MaxRetries) do
  begin
    Inc(Attempt);
    try
      Result := DoDownload(URL);   // may raise EDownloadError
      Done := True;
    except
      on E: Exception do
      begin
        if Attempt >= MaxRetries then
          raise;   // re-raise after final attempt
        Sleep(Attempt * 500);   // exponential-ish backoff
      end;
    end;
  end;
end;

// Pattern 2: Fallback / default value
function SafeReadInt(const SL: TStringList; const Key: string; Default: Integer): Integer;
begin
  try
    Result := StrToInt(SL.Values[Key]);
  except
    on EConvertError do
      Result := Default;   // swallow and use default
  end;
end;

// Pattern 3: Resource protection (always Free)
procedure ProcessFile(const Path: string);
var
  SL: TStringList;
begin
  SL := TStringList.Create;
  try
    SL.LoadFromFile(Path);
    Transform(SL);
    SL.SaveToFile(Path + '.bak');
  finally
    SL.Free;   // guaranteed cleanup
  end;
end;

// Pattern 4: Acquire multiple resources safely
procedure CopyFile(const Src, Dst: string);
var
  SrcList, DstList: TStringList;
begin
  SrcList := TStringList.Create;
  try
    SrcList.LoadFromFile(Src);
    DstList := TStringList.Create;
    try
      DstList.Assign(SrcList);
      DstList.SaveToFile(Dst);
    finally
      DstList.Free;
    end;
  finally
    SrcList.Free;
  end;
end;

// Pattern 5: Validation with multiple checks
procedure ValidateUser(const Name: string; Age: Integer);
begin
  if Name = '' then
    raise EArgumentException.Create('Name required');
  if Length(Name) > 50 then
    raise EArgumentException.Create('Name too long');
  if (Age < 0) or (Age > 150) then
    raise ERangeError.CreateFmt('Invalid age: %d', [Age]);
end;

Journalisation & Rapport d'erreurs

Un journaliseur de production a besoin de : (1) Sécurité des threads — TCriticalSection sérialise les écritures (plusieurs threads peuvent journaliser simultanément). (2) Niveaux de sévérité — l'énumération TLogLevel vous permet de filtrer (par ex., supprimer llDebug en production). (3) Sortie formatée — DateTime + niveau + message par ligne, analysable ultérieurement. (4) Vidage après chaque écriture — afin que les journaux survivent aux crashes (les écritures tamponnées non vidées sont perdues sur AV). (5) Journalisation des exceptions — LogException capture ClassName + Message + contexte. Le modèle journaliser-puis-relancer enregistre l'erreur mais laisse les couches supérieures la gérer. Pour une journalisation haute performance, envisagez les files sans verrou ou les bibliothèques externes (comme Log4Delphi). Libérez toujours le journaliseur dans finally pour fermer le handle de fichier.

delphi
uses
  System.SysUtils, System.Classes, System.IOUtils, System.SyncObjs;

type
  TLogLevel = (llDebug, llInfo, llWarning, llError, llFatal);

  TLogger = class
  private
    FLock: TCriticalSection;
    FFile: TextFile;
    FMinLevel: TLogLevel;
    function LevelToStr(L: TLogLevel): string;
  public
    constructor Create(const LogPath: string; MinLevel: TLogLevel);
    destructor Destroy; override;
    procedure Log(Level: TLogLevel; const Msg: string); overload;
    procedure Log(Level: TLogLevel; const Fmt: string; const Args: array of const); overload;
    procedure LogException(E: Exception; const Context: string);
  end;

constructor TLogger.Create(const LogPath: string; MinLevel: TLogLevel);
begin
  FLock := TCriticalSection.Create;
  FMinLevel := MinLevel;
  AssignFile(FFile, LogPath);
  if FileExists(LogPath) then
    Append(FFile)
  else
    Rewrite(FFile);
end;

destructor TLogger.Destroy;
begin
  CloseFile(FFile);
  FLock.Free;
  inherited;
end;

function TLogger.LevelToStr(L: TLogLevel): string;
begin
  case L of
    llDebug:   Result := 'DEBUG';
    llInfo:    Result := 'INFO';
    llWarning: Result := 'WARN';
    llError:   Result := 'ERROR';
    llFatal:   Result := 'FATAL';
  end;
end;

procedure TLogger.Log(Level: TLogLevel; const Msg: string);
begin
  if Level < FMinLevel then Exit;
  FLock.Enter;
  try
    WriteLn(FFile, Format('%s [%s] %s', [DateTimeToStr(Now), LevelToStr(Level), Msg]));
    Flush(FFile);   // ensure written to disk
  finally
    FLock.Leave;
  end;
end;

procedure TLogger.Log(Level: TLogLevel; const Fmt: string; const Args: array of const);
begin
  Log(Level, Format(Fmt, Args));
end;

procedure TLogger.LogException(E: Exception; const Context: string);
begin
  Log(llError, '%s: %s: %s', [Context, E.ClassName, E.Message]);
end;

// Usage
var
  Logger: TLogger;
begin
  Logger := TLogger.Create('app.log', llInfo);
  try
    Logger.Log(llInfo, 'Application started');
    try
      RiskyOperation;
    except
      on E: Exception do
      begin
        Logger.LogException(E, 'RiskyOperation');
        raise;   // log and re-raise
      end;
    end;
  finally
    Logger.Free;
  end;
end;
08

E/S Fichiers & Flux

Fichiers texte (Hérité & Moderne)

Deux approches : Héritée (AssignFile/Reset/Rewrite/ReadLn/WriteLn/CloseFile) est du Pascal classique — correct pour les E/S texte simples mais sujet aux erreurs (pas d'exceptions par défaut). Moderne (TFile dans System.IOUtils) est plus propre : WriteAllText, ReadAllText, ReadAllLines, AppendAllText, Exists. Les méthodes TFile lèvent des exceptions sur les erreurs (utilisez try...except). Pour les grands fichiers, utilisez StreamReader/StreamWriter (ligne par ligne, faible mémoire). Fermez toujours les fichiers (CloseFile pour l'hérité, ou utilisez try..finally). TFile est préféré pour le nouveau code — il est plus sûr et plus cohérent.

delphi
uses
  System.SysUtils, System.Classes, System.IOUtils;

// LEGACY: AssignFile / ReadLn / WriteLn (Pascal-style)
var
  F: TextFile;
  Line: string;
begin
  // write
  AssignFile(F, 'output.txt');
  Rewrite(F);                // create/overwrite
  try
    WriteLn(F, 'Hello, File!');
    WriteLn(F, 'Second line');
  finally
    CloseFile(F);
  end;

  // append
  AssignFile(F, 'output.txt');
  Append(F);
  try
    WriteLn(F, 'Appended line');
  finally
    CloseFile(F);
  end;

  // read line by line
  AssignFile(F, 'output.txt');
  Reset(F);                  // open for reading
  try
    while not EOF(F) do
    begin
      ReadLn(F, Line);
      WriteLn(Line);
    end;
  finally
    CloseFile(F);
  end;
end;

// MODERN: TFile (System.IOUtils)
var
  Content: string;
  Lines: TArray<string>;
begin
  // write all text
  TFile.WriteAllText('output.txt', 'Hello, World!');

  // append
  TFile.AppendAllText('log.txt', 'New entry' + sLineBreak);

  // read all text
  Content := TFile.ReadAllText('output.txt');

  // read all lines
  Lines := TFile.ReadAllLines('data.csv');
  for Line in Lines do
    WriteLn(Line);

  // write all lines
  TFile.WriteAllLines('nums.txt', ['one', 'two', 'three']);

  // file exists?
  if TFile.Exists('data.txt') then
    WriteLn('Found');
end;

TStringList pour fichiers & CSV

TStringList est la façon la plus simple de gérer les fichiers texte et les CSV simples. LoadFromFile/SaveToFile lisent/écrivent le fichier entier (une ligne par élément). CommaText divise/joint des valeurs séparées par des virgules ; DelimitedText utilise un Delimiter personnalisé. Values[] gère les paires clé=valeur (comme un fichier INI simple). Sorted=True trie automatiquement ; Find fait une recherche binaire (plus rapide que IndexOf sur les listes triées). Duplicates contrôle le comportement lors de l'ajout de doublons (dupIgnore, dupAccept, dupError). Pour les CSV complexes (champs entre guillemets avec virgules), utilisez un analyseur CSV dédié. TStringList est indexé à 0.

delphi
uses
  System.Classes;

var
  SL: TStringList;
  i: Integer;
begin
  SL := TStringList.Create;
  try
    // load a text file (one line per item)
    SL.LoadFromFile('data.txt');

    // iterate lines
    for i := 0 to SL.Count - 1 do
      WriteLn(SL[i]);

    // add and save
    SL.Add('New line');
    SL.SaveToFile('output.txt');

    // CSV handling (CommaText)
    SL.Clear;
    SL.CommaText := 'Alice,30,NYC';
    WriteLn(SL[0]);   // Alice
    WriteLn(SL[1]);   // 30
    WriteLn(SL[2]);   // NYC

    // custom delimiter
    SL.Clear;
    SL.Delimiter := '|';
    SL.DelimitedText := 'a|b|c';

    // key=value pairs (INI-style)
    SL.Clear;
    SL.Values['name'] := 'Alice';
    SL.Values['age'] := '30';
    WriteLn(SL.Values['name']);   // Alice
    SL.SaveToFile('config.ini');

    // sorted list (auto-sorts on Add)
    SL.Clear;
    SL.Sorted := True;
    SL.Add('cherry');
    SL.Add('apple');
    SL.Add('banana');
    // SL is now: apple, banana, cherry

    // find (binary search — list must be sorted)
    if SL.Find('banana', i) then
      WriteLn('Found at ', i);

    // duplicate handling
    SL.Duplicates := dupIgnore;   // ignore duplicates (sorted only)
    SL.Duplicates := dupError;    // raise on duplicates
  finally
    SL.Free;
  end;
end;

Flux & E/S binaires

TFileStream est une E/S d'octets de bas niveau (Read/Write de tampons, Position pour la recherche). TBinaryWriter/Reader écrivent/lisent des valeurs typées (Int32, Double, String, Boolean) — l'ordre de lecture doit correspondre à l'ordre d'écriture. TStreamReader/Writer gèrent le texte avec encodage (UTF-8, ASCII, Unicode) — utilisez-les pour les fichiers texte avec des caractères non ASCII. Tous les flux doivent être libérés (try..finally). fmCreate crée/écrase ; fmOpenRead ouvre en lecture seule ; fmOpenWrite ouvre pour l'écriture. Pour les grands fichiers, lisez ligne par ligne avec StreamReader (faible mémoire) au lieu de LoadFromFile (charge tout le fichier).

delphi
uses
  System.Classes, System.SysUtils;

var
  FS: TFileStream;
  BR: TBinaryReader;
  BW: TBinaryWriter;
  SR: TStreamReader;
  SW: TStreamWriter;
  Buffer: TBytes;
  i: Integer;
begin
  // TFileStream: low-level file access
  FS := TFileStream.Create('data.bin', fmCreate);   // fmCreate, fmOpenRead, fmOpenWrite
  try
    // write bytes
    SetLength(Buffer, 4);
    Buffer[0] := 1; Buffer[1] := 2; Buffer[2] := 3; Buffer[3] := 4;
    FS.Write(Buffer[0], Length(Buffer));

    // read
    FS.Position := 0;   // rewind
    SetLength(Buffer, 4);
    FS.Read(Buffer[0], 4);
  finally
    FS.Free;
  end;

  // TBinaryWriter / TBinaryReader (typed binary I/O)
  BW := TBinaryWriter.Create('data.bin');
  try
    BW.Write(42);          // Integer
    BW.Write(3.14);        // Double
    BW.Write('Hello');     // length-prefixed string
    BW.Write(True);        // Boolean
  finally
    BW.Free;
  end;

  BR := TBinaryReader.Create('data.bin');
  try
    WriteLn(BR.ReadInt32);    // 42
    WriteLn(BR.ReadDouble);   // 3.14
    WriteLn(BR.ReadString);   // Hello
    WriteLn(BR.ReadBoolean);  // TRUE
  finally
    BR.Free;
  end;

  // TStreamReader / TStreamWriter (text, with encoding)
  SW := TStreamWriter.Create('utf8.txt', False, TEncoding.UTF8);
  try
    SW.WriteLine('Hello, UTF-8!');
    SW.WriteLine('héllo wörld');
  finally
    SW.Free;
  end;

  SR := TStreamReader.Create('utf8.txt', TEncoding.UTF8);
  try
    while not SR.EndOfStream do
      WriteLn(SR.ReadLine);
  finally
    SR.Free;
  end;
end;

Opérations de répertoire & chemin

System.IOUtils fournit TPath, TFile, TDirectory pour les opérations de fichiers modernes. TPath.Combine joint les chemins en toute sécurité (multiplateforme). TPath.GetTempFileName crée un fichier temporaire unique. TDirectory.GetFiles prend en charge les modèles de recherche et la recherche récursive (soAllDirectories). TFile.Copy/Move/Delete sont des opérations de fichier simples. TFileInfo donne les métadonnées de fichier (taille, horodatages). Utilisez toujours les méthodes TPath au lieu de la concaténation de chaînes pour les chemins (gère les séparateurs correctement). Ces classes fonctionnent sur Windows, macOS et Linux (FireMonkey/FMX).

delphi
uses
  System.IOUtils, System.SysUtils;

var
  Files: TArray<string>;
  Dirs: TArray<string>;
  Path: string;
  i: Integer;
begin
  // TPath (cross-platform path handling)
  Path := TPath.Combine('folder', 'sub', 'file.txt');  // folder/sub/file.txt
  WriteLn(TPath.GetFileName('C:\temp\data.txt'));     // data.txt
  WriteLn(TPath.GetExtension('photo.JPG'));             // .JPG
  WriteLn(TPath.GetFileNameWithoutExtension('data.txt')); // data
  WriteLn(TPath.GetDirectoryName('C:\temp\data.txt'));  // C:\temp
  WriteLn(TPath.GetFullPath('data.txt'));               // absolute path

  // temp files
  WriteLn(TPath.GetTempFileName);   // creates a temp file
  WriteLn(TPath.GetTempPath);       // temp directory

  // special folders
  WriteLn(TPath.GetDocumentsPath);
  WriteLn(TPath.GetHomePath);

  // TDirectory
  TDirectory.CreateDirectory('backup\2024\june');

  Files := TDirectory.GetFiles('C:\temp', '*.txt');
  for i := 0 to High(Files) do
    WriteLn(Files[i]);

  // recursive search
  Files := TDirectory.GetFiles('C:\temp', '*.*', TSearchOption.soAllDirectories);

  Dirs := TDirectory.GetDirectories('C:\temp');
  for i := 0 to High(Dirs) do
    WriteLn(Dirs[i]);

  if TDirectory.Exists('old') then
    TDirectory.Delete('old', True);   // recursive delete

  // TFile operations
  TFile.Copy('source.txt', 'dest.txt', True);   // overwrite
  TFile.Move('old.txt', 'new.txt');
  TFile.Delete('unwanted.txt');

  // file info
  var Info: TFileInfo := TFileInfo.Create('data.txt');
  try
    WriteLn(Info.Length);          // size in bytes
    WriteLn(Info.CreationTime);
    WriteLn(Info.LastWriteTime);
    WriteLn(Info.Extension);
  finally
    Info.Free;
  end;
end;

Fichiers INI & JSON

TIniFile lit/écrit les fichiers de configuration INI (sections entre [crochets], clé=valeur). ReadString/ReadInteger/ReadBool ont des valeurs par défaut (retournées si la clé est manquante). Les fichiers INI sont des configurations simples et lisibles par l'homme — bons pour les préférences utilisateur. Pour les données structurées, utilisez JSON (System.JSON). TJSONObject construit/analyse les objets JSON ; TJSONArray pour les tableaux. AddPair ajoute clé-valeur ; GetValue<T> récupère les valeurs typées. ParseJSONValue analyse une chaîne JSON. JSON est idéal pour les API, les configurations complexes et l'échange de données. Pour les clients REST, utilisez TRESTClient ou les composants Indy.

delphi
uses
  System.IniFiles, System.JSON, System.SysUtils;

// INI files (simple config)
var
  Ini: TIniFile;
begin
  Ini := TIniFile.Create('config.ini');
  try
    // write
    Ini.WriteString('User', 'Name', 'Alice');
    Ini.WriteInteger('User', 'Age', 30);
    Ini.WriteBool('User', 'Active', True);
    Ini.WriteDateTime('Session', 'LastLogin', Now);

    // read (with defaults)
    WriteLn(Ini.ReadString('User', 'Name', 'Unknown'));   // Alice
    WriteLn(Ini.ReadInteger('User', 'Age', 0));            // 30
    WriteLn(Ini.ReadBool('User', 'Active', False));        // TRUE

    // read a whole section
    var SL: TStringList;
    SL := TStringList.Create;
    try
      Ini.ReadSection('User', SL);
      // SL = ['Name', 'Age', 'Active']
    finally
      SL.Free;
    end;
  finally
    Ini.Free;
  end;
end;

// JSON (System.JSON)
var
  Obj: TJSONObject;
  Arr: TJSONArray;
  JSON: string;
  i: Integer;
begin
  // build JSON
  Obj := TJSONObject.Create;
  try
    Obj.AddPair('name', 'Alice');
    Obj.AddPair('age', TJSONNumber.Create(30));
    Obj.AddPair('active', TJSONBool.Create(True));

    var Hobbies := TJSONArray.Create;
    Hobbies.Add('reading').Add('coding');
    Obj.AddPair('hobbies', Hobbies);

    JSON := Obj.ToJSON;
    // {"name":"Alice","age":30,"active":true,"hobbies":["reading","coding"]}
  finally
    Obj.Free;
  end;

  // parse JSON
  Obj := TJSONObject.ParseJSONValue(JSON) as TJSONObject;
  try
    WriteLn(Obj.GetValue<string>('name'));        // Alice
    WriteLn(Obj.GetValue<Integer>('age'));         // 30
    Arr := Obj.GetValue<TJSONArray>('hobbies');
    for i := 0 to Arr.Count - 1 do
      WriteLn(Arr.Items[i].Value);                 // reading, coding
  finally
    Obj.Free;
  end;
end;
09

Composants VCL approfondi

Cycle de vie des formulaires & composants

Les formulaires VCL suivent un cycle de vie strict : OnCreate (allouer des ressources, initialiser) → OnShow (le formulaire devient visible) → OnActivate → OnResize → OnPaint → ... → OnCloseQuery (peut annuler la fermeture) → OnClose → OnDestroy (libérer les ressources). Associez toujours OnCreate avec OnDestroy pour la gestion des ressources. OnCloseQuery vous permet d'empêcher la fermeture (définissez CanClose := False). Sender est le composant qui a déclenché l'événement. Les composants possèdent leurs enfants — libérer un formulaire libère automatiquement tous ses composants.

delphi
type
  TMainForm = class(TForm)
    Edit1: TEdit;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    FData: TStringList;
  public
    property Data: TStringList read FData;
  end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  FData := TStringList.Create;     // allocate in OnCreate
  Caption := 'My App v1.0';
end;

procedure TMainForm.FormShow(Sender: TObject);
begin
  Edit1.SetFocus;                  // focus when form is visible
end;

procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  CanClose := MessageDlg('Close?', mtConfirmation, [mbYes, mbNo], 0) = mrYes;
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  FData.Free;                      // free in OnDestroy (pairs with OnCreate)
end;

Contrôles VCL courants

VCL fournit un ensemble riche de contrôles : TEdit (texte sur une ligne), TMemo (texte multiligne), TLabel (texte non modifiable), TButton, TCheckBox, TRadioButton, TComboBox (liste déroulante), TListBox (liste sélectionnable). TStrings est la collection de fondation (Lines, Items sont des TStrings). ItemIndex sélectionne les éléments (basé sur 0, -1 = aucun). Styles de ComboBox : csDropDown (modifiable), csDropDownList (lecture seule). RadioGroup groupe les boutons radio avec un ItemIndex. Sorted trie automatiquement les éléments. PasswordChar masque l'entrée dans TEdit.

delphi
// Edit, Memo, Label, Button, CheckBox, RadioButton
Edit1.Text := 'Hello';
Edit1.MaxLength := 50;
Edit1.PasswordChar := '*';         // mask input

Memo1.Lines.Add('Line 1');         // TStrings collection
Memo1.Lines.LoadFromFile('notes.txt');
Memo1.WordWrap := True;
Memo1.ScrollBars := ssVertical;

// ComboBox & ListBox
ComboBox1.Items.Add('Option A');
ComboBox1.ItemIndex := 0;          // select first
ComboBox1.Style := csDropDownList; // read-only selection

ListBox1.Items.Add('Item 1');
ListBox1.Sorted := True;
ShowMessage(ListBox1.Items[ListBox1.ItemIndex]);

// CheckBox & RadioButton
if CheckBox1.Checked then
  ShowMessage('Checked');
RadioGroup1.Items.Add('Red');
RadioGroup1.Items.Add('Green');
RadioGroup1.ItemIndex := 0;

StringGrid & DBGrid

TStringGrid affiche des données tabulaires dans une grille de type tableur. Cells[Col, Row] accède aux cellules individuelles (indexé à 0). FixedRows/FixedCols créent des en-têtes non défilants. ColWidths/RowHeights personnalisent les tailles. Les options comme goEditing (cellules modifiables), goColSizing (redimensionner les colonnes), goRowSelect activent des comportements. OnDrawCell permet un rendu personnalisé avec le Canvas. TDBGrid se connecte directement à un DataSet (TTable, TQuery) via un TDataSource — il affiche et modifie automatiquement les enregistrements de base de données. Utilisez TDBGrid pour les données de base de données, TStringGrid pour les données en mémoire.

delphi
// TStringGrid - spreadsheet-like grid
StringGrid1.RowCount := 5;
StringGrid1.ColCount := 4;
StringGrid1.FixedRows := 1;        // header row
StringGrid1.FixedCols := 0;

// set headers
StringGrid1.Cells[0, 0] := 'Name';
StringGrid1.Cells[1, 0] := 'Age';
StringGrid1.Cells[2, 0] := 'City';

// populate data
StringGrid1.Cells[0, 1] := 'Alice';
StringGrid1.Cells[1, 1] := '30';
StringGrid1.Cells[2, 1] := 'NYC';

// customize appearance
StringGrid1.ColWidths[0] := 120;
StringGrid1.RowHeights[0] := 30;
StringGrid1.Options := StringGrid1.Options + [goEditing, goColSizing];

// onDrawCell for custom rendering
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
  Rect: TRect; State: TGridDrawState);
begin
  if ARow = 0 then
    StringGrid1.Canvas.Font.Style := [fsBold];
  StringGrid1.Canvas.TextRect(Rect, Rect.Left + 4, Rect.Top + 2,
    StringGrid1.Cells[ACol, ARow]);
end;

TTreeView & TListView

TTreeView affiche des données hiérarchiques (arbre) en utilisant des objets TTreeNode. AddChild crée des nœuds imbriqués. Expand(True) développe récursivement. GetNext traverse en profondeur d'abord ; GetNextSibling traverse niveau par niveau. BeginUpdate/EndUpdate regroupent les modifications pour la performance. TListView affiche les éléments dans différents styles de vue : vsIcon, vsSmallIcon, vsList, vsReport (colonnes). Caption est la première colonne ; SubItems contient les colonnes suivantes. Les deux prennent en charge le mode owner-data (virtuel) pour les grands jeux de données via les événements OnGetNodeData/OnData.

delphi
// TTreeView - hierarchical data
var
  RootNode, ChildNode: TTreeNode;
begin
  TreeView1.Items.BeginUpdate;
  try
    TreeView1.Items.Clear;
    RootNode := TreeView1.Items.Add(nil, 'Root');
    ChildNode := TreeView1.Items.AddChild(RootNode, 'Child 1');
    TreeView1.Items.AddChild(RootNode, 'Child 2');
    TreeView1.Items.AddChild(ChildNode, 'Grandchild');
    RootNode.Expand(True);         // expand all children
  finally
    TreeView1.Items.EndUpdate;
  end;

  // iterate
  var Node := TreeView1.Items.GetFirstNode;
  while Node <> nil do
  begin
    ShowMessage(Node.Text);
    Node := Node.GetNext;          // depth-first traversal
  end;
end;

// TListView - report view with columns
ListView1.ViewStyle := vsReport;
ListView1.Columns.Add.Caption := 'Name';
ListView1.Columns.Add.Caption := 'Size';
var Item := ListView1.Items.Add;
Item.Caption := 'file.txt';
Item.SubItems.Add('1.2 KB');

Dialogues & Composants courants

Delphi fournit des composants de dialogue standard : TOpenDialog/TSaveDialog (sélection de fichier), TOpenPictureDialog (aperçu d'image), TColorDialog, TFontDialog, TPrintDialog. Execute retourne True si l'utilisateur a cliqué sur OK. Filter définit les modèles de type de fichier ('Description|*.ext'). MessageDlg affiche des boîtes de message modales avec des types (mtInformation, mtWarning, mtError, mtConfirmation) et des ensembles de boutons ([mbYes, mbNo, mbOK, mbCancel]). InputBox/InputQuery obtiennent la saisie texte de l'utilisateur. TPageControl gère les interfaces à onglets avec des pages TTabSheet. Tous les dialogues sont des composants non visuels placés sur le formulaire.

delphi
// File dialogs
if OpenDialog1.Execute then
  ShowMessage('Selected: ' + OpenDialog1.FileName);

if SaveDialog1.Execute then
  ShowMessage('Save to: ' + SaveDialog1.FileName);

OpenDialog1.Filter := 'Text files (*.txt)|*.txt|All files (*.*)|*.*';
OpenDialog1.DefaultExt := 'txt';
OpenDialog1.Options := [ofFileMustExist, ofAllowMultiSelect];

// Color & Font dialogs
if ColorDialog1.Execute then
  Panel1.Color := ColorDialog1.Color;

if FontDialog1.Execute then
  Label1.Font := FontDialog1.Font;

// Message dialogs
case MessageDlg('Delete file?', mtWarning, [mbYes, mbNo, mbCancel], 0) of
  mrYes: DeleteFile('temp.txt');
  mrNo: ShowMessage('Cancelled');
end;

// InputBox & InputQuery
var Name := InputBox('Login', 'Enter name:', 'guest');
var Value: string;
if InputQuery('Settings', 'Port:', Value) then
  ShowMessage('Port: ' + Value);

// TPageControl (tabs)
var TabSheet := TTabSheet.Create(PageControl1);
TabSheet.PageControl := PageControl1;
TabSheet.Caption := 'Tab 1';
10

Programmation événementielle

Événements & Gestionnaires d'événements

Les événements en Delphi sont des pointeurs de méthode (procedure of object). TNotifyEvent est le type d'événement standard : procedure(Sender: TObject) of object. Les événements sont des propriétés — affectez les gestionnaires au moment de la conception (Inspecteur d'objets) ou à l'exécution. Vérifiez toujours Assigned() avant d'appeler un gestionnaire d'événement (il peut être nil si non affecté). Sender est l'objet qui a déclenché l'événement. Les événements personnalisés utilisent 'of object' pour se lier aux méthodes d'instance. Les paramètres var (comme var Key: Char dans OnKeyPress) permettent aux gestionnaires de modifier les valeurs — définissez Key := #0 pour supprimer l'entrée.

delphi
// Event type declaration
type
  TNotifyEvent = procedure(Sender: TObject) of object;
  TKeyPressEvent = procedure(Sender: TObject; var Key: Char) of object;

  TCounter = class
  private
    FValue: Integer;
    FOnChange: TNotifyEvent;
    FOnThresholdReached: TThresholdEvent;
  public
    property Value: Integer read FValue write SetValue;
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
  end;

procedure TCounter.SetValue(const NewValue: Integer);
begin
  if FValue <> NewValue then
  begin
    FValue := NewValue;
    if Assigned(FOnChange) then       // check before calling
      FOnChange(Self);                // trigger event
  end;
end;

// assigning handler at runtime
Counter1.OnChange := CounterChangeHandler;

procedure TForm1.CounterChangeHandler(Sender: TObject);
begin
  Label1.Caption := 'Value: ' + IntToStr((Sender as TCounter).Value);
end;

Délégués & Pointeurs de méthode

Les pointeurs de méthode ('of object') transportent à la fois l'adresse de la méthode et l'instance d'objet — ce sont des fermetures sur Self. Les pointeurs de procédure réguliers (sans 'of object') pointent vers des fonctions autonomes. Les pointeurs de méthode permettent les rappels, les modèles de stratégie et les systèmes d'événements. Affecter Op := Calc.Add stocke la référence ; appeler Op(10, 20) invoque Calc.Add sur l'instance Calc. Les méthodes anonymes (reference to function) sont une alternative moderne avec sémantique de fermeture. Les pointeurs de méthode sont l'épine dorsale de l'architecture VCL/FMX événementielle de Delphi.

delphi
type
  TMathFunc = function(X, Y: Integer): Integer of object;

  TCalculator = class
    function Add(X, Y: Integer): Integer;
    function Subtract(X, Y: Integer): Integer;
    function Multiply(X, Y: Integer): Integer;
  end;

function TCalculator.Add(X, Y: Integer): Integer;
begin
  Result := X + Y;
end;

// store and invoke method reference
var
  Calc: TCalculator;
  Op: TMathFunc;
begin
  Calc := TCalculator.Create;
  try
    Op := Calc.Add;           // method pointer
    ShowMessage(IntToStr(Op(10, 20)));  // 30

    Op := Calc.Subtract;
    ShowMessage(IntToStr(Op(10, 20)));  // -10
  finally
    Calc.Free;
  end;
end;

// regular procedure pointers (not of object)
type
  TSimpleFunc = function(X: Integer): Integer;
function DoubleIt(X: Integer): Integer;
begin
  Result := X * 2;
end;
var F: TSimpleFunc := DoubleIt;

Méthodes anonymes & Fermetures

Les méthodes anonymes (reference to function/procedure) sont des fermetures en ligne qui capturent les variables de leur portée englobante. Les types 'reference to' sont l'alternative moderne aux pointeurs de méthode — ils capturent les variables par référence, donc les modifications des variables capturées affectent la fermeture. Cela permet des modèles fonctionnels : map/filter/reduce, rappels et exécution différée. TFunc<T,TResult> et TProc<T> sont des alias génériques dans System.SysUtils. Les méthodes anonymes sont essentielles pour la programmation parallèle (PPL) et les idiomes Delphi modernes. Les variables capturées survivent à leur portée de déclaration.

delphi
type
  TFuncInt = reference to function(X: Integer): Integer;
  TProcStr = reference to procedure(const S: string);

procedure Apply(const Func: TFuncInt; Values: array of Integer);
var
  I: Integer;
begin
  for I := 0 to High(Values) do
    WriteLn(Func(Values[I]));
end;

var
  Multiplier: Integer;
  Double: TFuncInt;
begin
  Multiplier := 2;
  // anonymous method captures Multiplier (closure)
  Double := function(X: Integer): Integer
    begin
      Result := X * Multiplier;
    end;

  Apply(Double, [1, 2, 3, 4, 5]);   // 2, 4, 6, 8, 10

  Multiplier := 3;
  Apply(Double, [1, 2, 3]);          // 3, 6, 9 (captures by reference!)

  // anonymous procedure
  var Log: TProcStr := procedure(const S: string)
    begin
      WriteLn('[LOG] ' + S);
    end;
  Log('Hello');
end;

Gestion des messages & Messages Windows

VCL est construit sur les messages Windows. La directive 'message' gère des messages spécifiques (WM_LBUTTONDOWN, WM_KEYDOWN, etc.). Les enregistrements de message (TWMMouse, TWMKeyDown) sont des superpositions typées sur TMessage. Appelez toujours inherited pour laisser le traitement par défaut se produire (sauf si vous voulez supprimer le message). WndProc intercepte TOUS les messages avant la distribution — utilisez avec parcimonie pour les préoccupations transversales. PostMessage est asynchrone (retourne immédiatement) ; SendMessage est synchrone (attend le gestionnaire). WM_USER + N définit des messages personnalisés. C'est le fondement du modèle événementiel Windows.

delphi
type
  TMyForm = class(TForm)
  private
    procedure WMMouseDown(var Msg: TWMMouse); message WM_LBUTTONDOWN;
    procedure WMKeyDown(var Msg: TWMKeyDown); message WM_KEYDOWN;
    procedure WMNCHitTest(var Msg: TWMNCHitTest); message WM_NCHITTEST;
  protected
    procedure WndProc(var Message: TMessage); override;
  end;

// handle specific Windows message
procedure TMyForm.WMMouseDown(var Msg: TWMMouse);
begin
  inherited;                       // call default handler
  ShowMessage(Format('Click at %d, %d', [Msg.XPos, Msg.YPos]));
end;

// intercept all messages
procedure TMyForm.WndProc(var Message: TMessage);
begin
  if Message.Msg = WM_CLOSE then
  begin
    if MessageDlg('Close?', mtConfirmation, [mbYes, mbNo], 0) = mrNo then
      Exit;                       // swallow the message
  end;
  inherited WndProc(Message);     // pass to default
end;

// post/send custom messages
const
  WM_MYMESSAGE = WM_USER + 100;

PostMessage(Handle, WM_MYMESSAGE, 0, 0);   // async, returns immediately
SendMessage(Handle, WM_MYMESSAGE, 0, 0);   // sync, waits for handler

Événements d'application & Traitement en veille

TApplicationEvents centralise les événements au niveau de l'application : OnIdle (se déclenche lorsque la file de messages est vide), OnException (gestionnaire d'exceptions global), OnMinimize/OnRestore, OnHint (astuces de barre d'état), OnMessage (tous les messages Windows). OnIdle avec Done := False crée une boucle serrée ; utilisez avec précaution. TTimer déclenche OnTimer à intervalles (Interval en ms) — il est basé sur les messages, donc il ne se déclenchera pas pendant les opérations bloquantes. Application.ProcessMessages pompe la file de messages pendant les opérations longues (empêche 'Ne répond pas') mais peut causer des bugs de réentrance. TThread.Queue/Synchronize marshalent les mises à jour d'interface des threads d'arrière-plan vers le thread principal.

delphi
type
  TForm1 = class(TForm)
    ApplicationEvents1: TApplicationEvents;
    procedure AppIdle(Sender: TObject; var Done: Boolean);
    procedure AppException(Sender: TObject; E: Exception);
    procedure AppMinimize(Sender: TObject);
  end;

// OnIdle - runs when app has no pending messages
procedure TForm1.AppIdle(Sender: TObject; var Done: Boolean);
begin
  Label1.Caption := 'Idle...';
  Done := True;                    // False = keep calling Idle
end;

// global exception handler
procedure TForm1.AppException(Sender: TObject; E: Exception);
begin
  LogError(E.Message);
  ShowMessage('Error: ' + E.Message);
end;

// TTimer - periodic events
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  StatusBar1.Panels[0].Text := TimeToStr(Now);
end;

// TThread.Queue / TThread.Synchronize - marshal to main thread
TThread.Queue(nil,
  procedure
  begin
    Label1.Caption := 'Updated from background';
  end);

// ProcessMessages - pump message queue
while LongOperationRunning do
begin
  DoChunk;
  Application.ProcessMessages;     // keep UI responsive
end;
11

Accès aux bases de données avec FireDAC

Connexion & Bases de requête

FireDAC est le framework d'accès aux données universel moderne de Delphi prenant en charge SQLite, PostgreSQL, MySQL, SQL Server, Oracle, InterBase et plus. TFDConnection gère la connexion à la base de données (définissez DriverName et Params). TFDQuery exécute SQL avec des paramètres (syntaxe :param) — utilisez toujours des paramètres pour empêcher l'injection SQL. ExecSQL exécute INSERT/UPDATE/DELETE/DDL (pas de jeu de résultats) ; Open exécute SELECT (retourne un curseur). FieldByName('col').AsString/AsInteger lit les valeurs. Naviguez avec Next/Prev/First/Last ; Eof marque la fin. FireDAC remplace les anciennes technologies dbExpress et BDE.

delphi
uses
  FireDAC.Comp.Client, FireDAC.Comp.DataSet, FireDAC.Stan.Param;

var
  FDConn: TFDConnection;
  Query: TFDQuery;
begin
  FDConn := TFDConnection.Create(nil);
  Query := TFDQuery.Create(nil);
  try
    // connection string (SQLite example)
    FDConn.DriverName := 'SQLite';
    FDConn.Params.Database := 'app.db';
    FDConn.Connected := True;

    Query.Connection := FDConn;

    // execute non-query (DDL/DML)
    Query.ExecSQL('CREATE TABLE IF NOT EXISTS users ' +
      '(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)');

    // parameterized insert (prevents SQL injection)
    Query.SQL.Text := 'INSERT INTO users (name, age) VALUES (:name, :age)';
    Query.ParamByName('name').AsString := 'Alice';
    Query.ParamByName('age').AsInteger := 30;
    Query.ExecSQL;

    // select with parameters
    Query.SQL.Text := 'SELECT * FROM users WHERE age > :minAge';
    Query.ParamByName('minAge').AsInteger := 18;
    Query.Open;

    while not Query.Eof do
    begin
      ShowMessage(Query.FieldByName('name').AsString);
      Query.Next;
    end;
  finally
    Query.Free;
    FDConn.Free;
  end;
end;

TFDTable & Live Bindings

TFDTable ouvre une table entière (SELECT * FROM tablename) — pratique pour un CRUD simple mais moins efficace que TFDQuery pour les grandes tables. Navigation dans le dataset : First/Next/Prior/Last/MoveBy. Locate recherche par valeurs de champ (retourne True si trouvé). Édition : Append/Insert (nouvelle ligne) ou Edit (existante), puis définissez les champs, puis Post (valider) ou Cancel (annuler). Filter restreint les lignes visibles (côté client). IndexFieldNames trie les enregistrements. Connectez TFDTable/TFDQuery à TDataSource, puis à TDBGrid/TDBEdit pour une interface utilisateur automatique sensible aux données. Les Live Bindings (FMX) fournissent une liaison visuelle des contrôles aux champs de données.

delphi
var
  Table: TFDTable;
begin
  Table := TFDTable.Create(nil);
  try
    Table.Connection := FDConn;
    Table.TableName := 'users';
    Table.Open;                    // SELECT * FROM users

    // navigate
    Table.First;
    while not Table.Eof do
    begin
      ShowMessage(Table.FieldByName('name').AsString);
      Table.Next;
    end;

    // locate a record
    if Table.Locate('name', 'Alice', []) then
      ShowMessage('Found Alice');

    // edit/insert/post
    Table.Append;                  // or Insert / Edit
    Table.FieldByName('name').AsString := 'Bob';
    Table.FieldByName('age').AsInteger := 25;
    Table.Post;                    // commit to dataset

    // filter
    Table.Filter := 'age > 20';
    Table.Filtered := True;

    // index for sorting
    Table.IndexFieldNames := 'name';
  finally
    Table.Free;
  end;
end;

// connect to DBGrid via DataSource
DataSource1.DataSet := Table;
DBGrid1.DataSource := DataSource1;

Transactions & Opérations par lot

Les transactions garantissent l'atomicité — toutes les opérations réussissent ou aucune ne le fait. StartTransaction/Commit/Rollback enveloppent les opérations liées. Sans transactions explicites, FireDAC auto-valide chaque instruction (lent pour les insertions en masse). Array DML (Execute(count, startAt)) envoie des lots paramétrés en un seul aller-retour — considérablement plus rapide pour les insertions en masse (accélération 10-100x). Enveloppez toujours les transactions dans try/except pour Rollback en cas d'échec. Pour les transactions longues, envisagez les niveaux d'isolation (xiReadCommitted, xiRepeatableRead). Le pool de connexions (TFDManager) améliore les performances multithread.

delphi
var
  FDConn: TFDConnection;
  Query: TFDQuery;
  I: Integer;
begin
  FDConn := TFDConnection.Create(nil);
  Query := TFDQuery.Create(nil);
  try
    FDConn.DriverName := 'SQLite';
    FDConn.Params.Database := 'app.db';
    FDConn.Connected := True;
    Query.Connection := FDConn;

    // explicit transaction
    FDConn.StartTransaction;
    try
      Query.SQL.Text := 'INSERT INTO users (name, age) VALUES (:n, :a)';
      for I := 1 to 1000 do
      begin
        Query.ParamByName('n').AsString := 'User' + IntToStr(I);
        Query.ParamByName('a').AsInteger := 20 + (I mod 50);
        Query.ExecSQL;
      end;
      FDConn.Commit;               // commit all
    except
      FDConn.Rollback;             // undo all on error
      raise;
    end;

    // batch execute (Array DML - very fast)
    Query.SQL.Text := 'INSERT INTO logs (msg) VALUES (:m)';
    Query.Params.ArraySize := 100;
    for I := 0 to 99 do
      Query.Params[0].AsStrings[I] := 'Log entry ' + IntToStr(I);
    Query.Execute(100, 0);         // execute 100 times at once
  finally
    Query.Free;
    FDConn.Free;
  end;
end;

Procédures stockées & Métadonnées

TFDStoredProc appelle des procédures stockées de base de données. Définissez StoredProcName et les paramètres (ParamType : ptInput, ptOutput, ptInputOutput, ptResult). ExecProc exécute les procédures qui ne retournent pas de curseurs ; Open exécute celles qui retournent des jeux de résultats. Les procédures stockées encapsulent la logique métier côté serveur pour la performance et la sécurité. TFDMetaInfoQuery interroge le schéma de base de données (tables, colonnes, index, contraintes) — utile pour construire des outils dynamiques, des ORM ou des navigateurs de schéma. Options MetaInfoKind : mkTables, mkColumns, mkIndexes, mkPrimaryKey, mkForeignKeys. FireDAC prend aussi en charge la mise en cache de schéma pour l'accès aux métadonnées hors ligne.

delphi
// call stored procedure
var
  SP: TFDStoredProc;
begin
  SP := TFDStoredProc.Create(nil);
  try
    SP.Connection := FDConn;
    SP.StoredProcName := 'get_user_by_id';
    SP.Params.ParamByName('@user_id').AsInteger := 42;

    // output parameter
    SP.Params.ParamByName('@name').ParamType := ptOutput;

    SP.ExecProc;                   // execute (no cursor)
    ShowMessage(SP.ParamByName('@name').AsString);

    // or Open if it returns a result set
    SP.Open;
    ShowMessage(SP.FieldByName('name').AsString);
  finally
    SP.Free;
  end;
end;

// metadata - list tables
var
  Meta: TFDMetaInfoQuery;
begin
  Meta := TFDMetaInfoQuery.Create(nil);
  try
    Meta.Connection := FDConn;
    Meta.MetaInfoKind := mkTables;     // or mkColumns, mkIndexes
    Meta.Open;
    while not Meta.Eof do
    begin
      ShowMessage(Meta.FieldByName('TABLE_NAME').AsString);
      Meta.Next;
    end;
  finally
    Meta.Free;
  end;
end;

Table mémoire FireDAC & SQL local

TFDMemTable est un dataset en mémoire — parfait pour la mise en cache, les données temporaires et les tests unitaires sans base de données. Définissez les champs avec FieldDefs, puis CreateDataSet. AppendRecord ajoute des lignes. Prend en charge les index, les filtres et toute la navigation de dataset. Local SQL (TFDLocalSQL) vous permet d'exécuter des requêtes SQL sur n'importe quel TDataSet (y compris TFDMemTable, TClientDataSet, même Excel via ODBC) — permettant des jointures entre tables mémoire et tables de base de données. C'est puissant pour l'ETL, le reporting et la construction de couches de données qui fonctionnent hors ligne. TFDMemTable peut aussi charger/sauvegarder vers des fichiers binaires ou JSON pour la persistance.

delphi
uses FireDAC.Comp.Client, FireDAC.Stan.Intf;

var
  MemTable: TFDMemTable;
begin
  MemTable := TFDMemTable.Create(nil);
  try
    // define schema in code
    MemTable.FieldDefs.Add('id', ftInteger);
    MemTable.FieldDefs.Add('name', ftString, 50);
    MemTable.FieldDefs.Add('salary', ftCurrency);
    MemTable.CreateDataSet;        // create in-memory table

    // populate
    MemTable.AppendRecord([1, 'Alice', 75000]);
    MemTable.AppendRecord([2, 'Bob', 68000]);
    MemTable.AppendRecord([3, 'Carol', 82000]);

    // index & filter
    MemTable.IndexFieldNames := 'salary';
    MemTable.Filter := 'salary > 70000';
    MemTable.Filtered := True;

    // Local SQL - query any TDataSet with SQL
    var LocalSQL := TFDLocalSQL.Create(nil);
    try
      LocalSQL.Connection := FDConn;  // or a dedicated connection
      LocalSQL.DataSets.AddDataSet(MemTable, 'employees');
      LocalSQL.Active := True;

      var Q := TFDQuery.Create(nil);
      try
        Q.Connection := FDConn;
        Q.Open('SELECT * FROM employees WHERE salary > :min ORDER BY name');
        // query in-memory data with full SQL!
      finally
        Q.Free;
      end;
    finally
      LocalSQL.Free;
    end;
  finally
    MemTable.Free;
  end;
end;
12

Génériques & Méthodes anonymes

Classes & Méthodes génériques

Les génériques (introduits dans Delphi 2009) permettent des conteneurs et algorithmes de type sûr. TStack<T> fonctionne avec n'importe quel type T — le compilateur génère des versions spécialisées. Cela élimine les transtypages à l'exécution (pas de transtypage TObject) et détecte les erreurs de type à la compilation. Les paramètres de type générique utilisent la syntaxe <T>. Les méthodes, classes, enregistrements et interfaces génériques sont tous pris en charge. Les contraintes (class, constructor, interface) restreignent les types utilisables. La RTL fournit TList<T>, TDictionary<TKey,TValue>, TQueue<T>, TStack<T>, TObjectList<T> dans System.Generics.Collections.

delphi
type
  TStack<T> = class
  private
    FItems: array of T;
    FCount: Integer;
  public
    procedure Push(const Value: T);
    function Pop: T;
    function Peek: T;
    function Count: Integer;
  end;

procedure TStack<T>.Push(const Value: T);
begin
  if FCount = Length(FItems) then
    SetLength(FItems, FCount * 2 + 4);
  FItems[FCount] := Value;
  Inc(FCount);
end;

function TStack<T>.Pop: T;
begin
  if FCount = 0 then
    raise Exception.Create('Stack empty');
  Dec(FCount);
  Result := FItems[FCount];
end;

// usage - type-safe, no casts
var
  IntStack: TStack<Integer>;
  StrStack: TStack<string>;
begin
  IntStack := TStack<Integer>.Create;
  IntStack.Push(42);
  IntStack.Push(99);
  ShowMessage(IntToStr(IntStack.Pop));  // 99

  StrStack := TStack<string>.Create;
  StrStack.Push('Hello');
  ShowMessage(StrStack.Pop);             // Hello
end;

Contraintes génériques

Les contraintes génériques restreignent les paramètres de type : 'class' (doit être un type classe), 'constructor' (doit avoir un constructeur Create sans paramètre — permet T.Create), 'record' (doit être un type valeur), noms d'interface (doit implémenter l'interface). Les contraintes multiples sont séparées par des virgules. Les contraintes permettent d'appeler des méthodes sur T (par ex., T.Create avec la contrainte constructeur). Sans contraintes, vous ne pouvez qu'affecter/comparer T (pas d'appels de méthode). L'inférence de type vous permet parfois d'omettre les paramètres de type explicites. Les contraintes sont essentielles pour construire des frameworks et ORM de type sûr.

delphi
type
  // T must be a class
  TRepository<T: class> = class
    function Find(Id: Integer): T;
  end;

  // T must be a class with a parameterless constructor
  TFactory<T: class, constructor> = class
    function CreateInstance: T;
  end;

  // T must implement IComparable
  TSorter<T: IComparable> = class
    procedure Sort(var Arr: array of T);
  end;

  // multiple constraints
  TManager<T: class, constructor, IComparable> = class
  end;

function TFactory<T>.CreateInstance: T;
begin
  Result := T.Create;    // allowed because of 'constructor' constraint
end;

// type inference
type
  TPair<TKey, TValue> = class
    Key: TKey;
    Value: TValue;
    constructor Create(const K: TKey; const V: TValue);
  end;

var
  P: TPair<string, Integer>;
begin
  P := TPair<string, Integer>.Create('age', 30);
end;

TList<T> & TDictionary<TKey,TValue>

System.Generics.Collections fournit des conteneurs de type sûr : TList<T> (tableau dynamique), TDictionary<TKey,TValue> (table de hachage), TQueue<T> (FIFO), TStack<T> (LIFO), TObjectList<T> (possède ses objets — les libère automatiquement). Sort utilise la comparaison par défaut ; TComparer<T>.Construct crée des comparateurs personnalisés avec des méthodes anonymes. FindIndex/recherche basée sur prédicat utilise des fonctions anonymes de prédicat. TryGetValue retourne True et sort la valeur si trouvée (évite l'exception). AddOrSetValue met à jour ou insère. TObjectList<T> avec OwnsObjects := True libère automatiquement les objets contenus lorsque la liste est libérée — empêchant les fuites de mémoire.

delphi
uses System.Generics.Collections, System.Generics.Defaults;

var
  List: TList<Integer>;
  Dict: TDictionary<string, Integer>;
  ObjList: TObjectList<TPerson>;
begin
  // TList<T>
  List := TList<Integer>.Create;
  try
    List.AddRange([3, 1, 4, 1, 5, 9, 2, 6]);
    List.Sort;                     // 1, 1, 2, 3, 4, 5, 6, 9
    List.BinarySearch(5, var Idx); // fast lookup in sorted list

    // custom comparer
    List.Sort(TComparer<Integer>.Construct(
      function(const L, R: Integer): Integer
      begin
        Result := R - L;           // descending
      end));

    // find with predicate
    var Found := List.FindIndex(
      function(const X: Integer): Boolean
      begin
        Result := X > 4;
      end);
  finally
    List.Free;
  end;

  // TDictionary
  Dict := TDictionary<string, Integer>.Create;
  try
    Dict.Add('apple', 5);
    Dict.Add('banana', 3);
    Dict.AddOrSetValue('apple', 10);  // update or insert

    if Dict.TryGetValue('banana', var Count) then
      ShowMessage(Count.ToString);

    // iterate
    for var Pair in Dict do
      ShowMessage(Pair.Key + ': ' + Pair.Value.ToString);
  finally
    Dict.Free;
  end;
end;

Méthodes anonymes comme rappels

Les méthodes anonymes permettent la programmation fonctionnelle en Delphi. Les types 'reference to function' sont des fermetures — elles capturent les variables de leur portée englobante. Les fonctions d'ordre supérieur comme Map et Filter prennent des fonctions en paramètres, permettant des transformations de données concises. TFunc<T,TResult> et TProc<T> sont des types délégués génériques intégrés. Les fermetures capturent les variables par référence, donc elles reflètent les modifications ultérieures. Ce modèle remplace les interfaces de rappel verbeuses et est essentiel pour la PPL (Bibliothèque de Programmation Parallèle), les gestionnaires d'événements et les opérations de type LINQ. Les méthodes anonymes sont à comptage de références et gérées automatiquement.

delphi
uses System.SysUtils;

type
  TFunc<T, TResult> = reference to function(Arg: T): TResult;
  TProc<T> = reference to procedure(Arg: T);

// higher-order functions
function Map<T, TResult>(const Source: array of T;
  const Mapper: TFunc<T, TResult>): TArray<TResult>;
var
  I: Integer;
begin
  SetLength(Result, Length(Source));
  for I := 0 to High(Source) do
    Result[I] := Mapper(Source[I]);
end;

function Filter<T>(const Source: array of T;
  const Predicate: TFunc<T, Boolean>): TArray<T>;
var
  I, Count: Integer;
begin
  Count := 0;
  SetLength(Result, Length(Source));
  for I := 0 to High(Source) do
    if Predicate(Source[I]) then
    begin
      Result[Count] := Source[I];
      Inc(Count);
    end;
  SetLength(Result, Count);
end;

// usage with closures
var
  Numbers: array of Integer;
  Doubled, Evens: TArray<Integer>;
  Threshold: Integer;
begin
  Numbers := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
  Threshold := 5;

  Doubled := Map<Integer, Integer>(Numbers,
    function(X: Integer): Integer
    begin
      Result := X * 2;
    end);

  // closure captures Threshold
  Evens := Filter<Integer>(Numbers,
    function(X: Integer): Boolean
    begin
      Result := (X > Threshold) and (X mod 2 = 0);
    end);
end;

Interfaces génériques & TComparer

Les interfaces génériques permettent des contrats de type sûr : IRepository<T> fonctionne avec n'importe quel type d'entité. Combiné avec le comptage de références (TInterfacedObject), cela fournit une gestion automatique de la mémoire — les interfaces sont à comptage de références, libérées lorsque la dernière référence disparaît. TComparer<T>.Construct crée un IComparer<T> à partir d'une fonction de comparaison anonyme — utilisé par Sort, BinarySearch et SortedDictionary. Les interfaces génériques sont le fondement de l'injection de dépendances en Delphi (enregistrez IRepository<TUser>, injectez dans les services). Le framework Spring4D étend cela avec un conteneur DI complet. Les contraintes génériques (class, constructor) garantissent que T peut être instancié.

delphi
type
  IComparable<T> = interface
    function CompareTo(const Other: T): Integer;
  end;

  IRepository<T> = interface
    function GetById(Id: Integer): T;
    function GetAll: TArray<T>;
    procedure Save(const Entity: T);
    procedure Delete(Id: Integer);
  end;

  TMemoryRepository<T: class, constructor> = class(TInterfacedObject, IRepository<T>)
  private
    FItems: TList<T>;
  public
    constructor Create;
    destructor Destroy; override;
    function GetById(Id: Integer): T;
    function GetAll: TArray<T>;
    procedure Save(const Entity: T);
    procedure Delete(Id: Integer);
  end;

// custom comparer for sorting objects
type
  TPerson = class
    Name: string;
    Age: Integer;
  end;

var
  People: TObjectList<TPerson>;
begin
  People := TObjectList<TPerson>.Create;
  People.Sort(TComparer<TPerson>.Construct(
    function(const L, R: TPerson): Integer
    begin
      Result := CompareText(L.Name, R.Name);  // sort by name
    end));
end;
13

RTTI & Réflexion

Bases du RTTI étendu

Le RTTI étendu (Runtime Type Information), introduit dans Delphi 2010, fournit une réflexion complète : inspecter les types, propriétés, méthodes et champs à l'exécution. TRTTIContext est le point d'entrée. GetType retourne TRttiType pour une classe. GetProperties énumère les propriétés published. GetValue/SetValue lisent/écrivent les valeurs de propriété dynamiquement en utilisant TValue (un type de type variant). Seuls les membres 'published' ont du RTTI par défaut (utilisez la directive {$RTTI EXPLICIT ...} pour plus). Le RTTI alimente la sérialisation (JSON/XML), les ORM, l'injection de dépendances et les concepteurs visuels. Il a un petit surcoût de performance mais permet un métaprogrammage puissant.

delphi
uses System.RTTI, System.TypInfo;

type
  TPerson = class
  private
    FName: string;
    FAge: Integer;
  published
    property Name: string read FName write FName;
    property Age: Integer read FAge write FAge;
  end;

var
  Ctx: TRTTIContext;
  RType: TRttiType;
  Prop: TRttiProperty;
  Person: TPerson;
begin
  Person := TPerson.Create;
  try
    Person.Name := 'Alice';
    Person.Age := 30;

    Ctx := TRTTIContext.Create;
    try
      RType := Ctx.GetType(TPerson);

      // enumerate properties
      for Prop in RType.GetProperties do
      begin
        WriteLn(Prop.Name, ': ', Prop.PropertyType.Name);

        // read value
        if Prop.IsReadable then
          WriteLn('  Value: ', Prop.GetValue(Person).ToString);

        // write value
        if Prop.IsWritable then
          Prop.SetValue(Person, TValue.From<string>('Bob'));
      end;

      // get specific property
      Prop := RType.GetProperty('Name');
      ShowMessage(Prop.GetValue(Person).AsString);
    finally
      Ctx.Free;
    end;
  finally
    Person.Free;
  end;
end;

Invocation de méthodes & Attributs

Le RTTI peut invoquer des méthodes dynamiquement via TRttiMethod.Invoke — passez les arguments comme un tableau TValue. Les attributs (sous-classes de TCustomAttribute) attachent des métadonnées aux types, propriétés et méthodes en utilisant la syntaxe [Attribute]. GetAttributes les récupère à l'exécution. Cela permet les frameworks de validation ([Required], [MaxLength]), le mappage ORM ([Table], [Column]) et le contrôle de sérialisation ([JsonProperty]). Les attributs sont une fonctionnalité de métaprogrammation puissante — le compilateur les stocke dans le RTTI, et les frameworks les lisent pour piloter le comportement. L'invocation de méthodes via RTTI est plus lente que les appels directs mais essentielle pour le scripting, l'injection de dépendances et le dispatch dynamique.

delphi
uses System.RTTI;

type
  TValidatorAttribute = class(TCustomAttribute)
  private
    FMaxLen: Integer;
  public
    constructor Create(MaxLen: Integer);
    property MaxLen: Integer read FMaxLen;
  end;

  TUser = class
  private
    FName: string;
  public
    [Validator(50)]
    property Name: string read FName write FName;

    function Greet(const Greeting: string): string;
  end;

constructor TValidatorAttribute.Create(MaxLen: Integer);
begin
  FMaxLen := MaxLen;
end;

var
  Ctx: TRTTIContext;
  RType: TRttiType;
  Prop: TRttiProperty;
  Attr: TCustomAttribute;
  Method: TRttiMethod;
  User: TUser;
  Result: TValue;
begin
  User := TUser.Create;
  User.Name := 'Alice';
  Ctx := TRTTIContext.Create;
  try
    RType := Ctx.GetType(TUser);

    // read attributes
    Prop := RType.GetProperty('Name');
    for Attr in Prop.GetAttributes do
    begin
      if Attr is TValidatorAttribute then
        WriteLn('Max length: ', TValidatorAttribute(Attr).MaxLen);
    end;

    // invoke method by name
    Method := RType.GetMethod('Greet');
    Result := Method.Invoke(User, ['Hello']);
    ShowMessage(Result.AsString);  // Hello, Alice
  finally
    Ctx.Free;
    User.Free;
  end;
end;

Découverte & Énumération de types

TRTTIContext.GetTypes énumère tous les types avec RTTI dans le programme compilé — utile pour la découverte de plugins, le scan de modèles ORM et la construction de navigateurs de types. FindType localise un type par nom qualifié ('UnitName.TypeName'). TRttiType fournit GetFields (tous les champs), GetMethods (toutes les méthodes), GetProperties (propriétés published). TypeKind distingue les classes, enregistrements, interfaces, énumérations, etc. AsInstance.MetaclassType donne la référence de classe pour l'instanciation. Cela permet aux frameworks de découvrir et de câbler automatiquement les composants. Les frameworks Spring4D et DORM utilisent cela pour le mappage ORM automatique. L'énumération RTTI est lente — mettez en cache les résultats pour une utilisation répétée.

delphi
uses System.RTTI;

var
  Ctx: TRTTIContext;
  Types: TArray<TRttiType>;
  T: TRttiType;
  Field: TRttiField;
  Method: TRttiMethod;
begin
  Ctx := TRTTIContext.Create;
  try
    // enumerate ALL types in the program
    Types := Ctx.GetTypes;

    // find types by name
    T := Ctx.FindType('Unit1.TPerson');
    if T <> nil then
      ShowMessage('Found: ' + T.QualifiedName);

    // filter: all classes in a unit
    for T in Types do
    begin
      if (T.TypeKind = tkClass) and T.QualifiedName.StartsWith('MyApp.') then
      begin
        WriteLn('Class: ', T.Name);

        // enumerate fields
        for Field in T.GetFields do
          WriteLn('  Field: ', Field.Name, ': ', Field.FieldType.Name);

        // enumerate methods
        for Method in T.GetMethods do
          WriteLn('  Method: ', Method.Name,
            ' - ', Method.MethodType.ToString);
      end;
    end;

    // create instance via RTTI
    var Instance := T.AsInstance.MetaclassType.Create;
    try
      // use instance...
    finally
      Instance.Free;
    end;
  finally
    Ctx.Free;
  end;
end;

TValue & Typage dynamique

TValue est le type de valeur dynamique de Delphi — une union étiquetée contenant n'importe quel type avec ses informations de type. From<T> enveloppe une valeur ; AsType<T>/AsInteger/AsString la déroule. IsType<T> vérifie le type. TryAsType tente une conversion sûre. TValue est essentiel pour le RTTI (valeurs de propriétés, arguments de méthodes) et permet le typage dynamique dans un langage statiquement typé. C'est similaire à 'object' de C# avec des infos de type, ou à la nature dynamique de Python. TValue gère les primitives, les chaînes, les objets, les tableaux et les enregistrements. Utilisez-le pour construire des sérialiseurs, des moteurs de script ou des couches de données génériques. Il a un surcoût par rapport au typage direct mais fournit une flexibilité maximale.

delphi
uses System.RTTI;

var
  V: TValue;
  I: Integer;
  S: string;
  D: Double;
  Obj: TObject;
begin
  // wrap values
  V := TValue.From<Integer>(42);
  ShowMessage(V.ToString);          // '42'
  I := V.AsInteger;                 // unwrap

  V := TValue.From<string>('Hello');
  S := V.AsString;

  // type checking
  if V.IsType<string> then
    ShowMessage('It is a string');

  // conversion
  V := TValue.From<Integer>(100);
  D := V.AsExtended;                // 100.0

  // boxing objects
  var Person := TPerson.Create;
  try
    V := TValue.From<TPerson>(Person);
    if V.IsObject then
      ShowMessage(V.AsObject.ClassName);  // 'TPerson'
  finally
    Person.Free;
  end;

  // array of TValue for method invocation
  var Args: array of TValue;
  SetLength(Args, 2);
  Args[0] := TValue.From<Integer>(10);
  Args[1] := TValue.From<Integer>(20);

  // try conversion
  V := TValue.From<string>('123');
  if V.TryAsType<Integer>(I) then
    ShowMessage(IntToStr(I));       // 123
end;

Sérialisation avec RTTI

Le RTTI permet la sérialisation automatique — conversion d'objets vers/depuis JSON, XML ou n'importe quel format sans code de mappage manuel. ObjectToJSON itère les propriétés published, lit les valeurs via RTTI et construit un TJSONObject. JSONToObject inverse le processus. Ce modèle alimente les clients REST, les systèmes de configuration et les couches ORM. L'unité REST.json fournit TJson.ObjectToJsonString et TJson.JsonToObject pour cela prêt à l'emploi. Pour un usage en production, ajoutez des attributs ([JsonProperty('name')]) pour contrôler les noms de champs, et gérez les objets imbriqués, les tableaux et les types personnalisés. La sérialisation basée sur RTTI est plus lente que les mappeurs écrits à la main mais beaucoup plus maintenable.

delphi
uses System.RTTI, System.JSON;

function ObjectToJSON(Obj: TObject): TJSONObject;
var
  Ctx: TRTTIContext;
  RType: TRttiType;
  Prop: TRttiProperty;
  Val: TValue;
begin
  Result := TJSONObject.Create;
  Ctx := TRTTIContext.Create;
  try
    RType := Ctx.GetType(Obj.ClassType);
    for Prop in RType.GetProperties do
    begin
      if not Prop.IsReadable then Continue;
      Val := Prop.GetValue(Obj);
      case Val.Kind of
        tkString, tkUString:
          Result.AddPair(Prop.Name, Val.AsString);
        tkInteger:
          Result.AddPair(Prop.Name, TJSONNumber.Create(Val.AsInteger));
        tkFloat:
          Result.AddPair(Prop.Name, TJSONNumber.Create(Val.AsExtended));
        tkEnumeration:
          Result.AddPair(Prop.Name, TJSONBool.Create(Val.AsBoolean));
      end;
    end;
  finally
    Ctx.Free;
  end;
end;

procedure JSONToObject(Obj: TObject; const JSON: TJSONObject);
var
  Ctx: TRTTIContext;
  RType: TRttiType;
  Prop: TRttiProperty;
  Pair: TJSONPair;
begin
  Ctx := TRTTIContext.Create;
  try
    RType := Ctx.GetType(Obj.ClassType);
    for Prop in RType.GetProperties do
    begin
      if not Prop.IsWritable then Continue;
      Pair := JSON.FindPair(Prop.Name);
      if Pair <> nil then
      begin
        case Prop.PropertyType.TypeKind of
          tkString, tkUString:
            Prop.SetValue(Obj, TValue.From<string>(Pair.JsonValue.Value));
          tkInteger:
            Prop.SetValue(Obj, TValue.From<Integer>((Pair.JsonValue as TJSONNumber).AsInt));
        end;
      end;
    end;
  finally
    Ctx.Free;
  end;
end;
14

Interfaces & COM

Bases des interfaces & Comptage de références

Les interfaces définissent des contrats (signatures de méthodes) sans implémentation. TInterfacedObject fournit le comptage de références — lorsque la dernière référence d'interface disparaît, l'objet est libéré automatiquement (pas besoin d'appeler Free). C'est la gestion automatique de la mémoire de Delphi pour les objets d'interface. Les GUID (['{...}']) permettent l'interop COM et les vérifications InterfaceAs/Supports. Une classe peut implémenter plusieurs interfaces (TShape implémente à la fois IMovable et IDrawable). Les propriétés d'interface sont autorisées (doivent avoir des méthodes read/write). Utilisez toujours des types d'interface (IMovable) et non des types de classe (TShape) pour que le comptage de références fonctionne. Mélanger des références d'objet et d'interface peut causer une libération prématurée.

delphi
type
  IMovable = interface
    ['{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}']  // GUID (optional)
    procedure MoveTo(X, Y: Integer);
    function GetPosition: TPoint;
    property Position: TPoint read GetPosition;
  end;

  IDrawable = interface
    procedure Draw(Canvas: TCanvas);
  end;

  TShape = class(TInterfacedObject, IMovable, IDrawable)
  private
    FX, FY: Integer;
  public
    procedure MoveTo(X, Y: Integer);
    function GetPosition: TPoint;
    procedure Draw(Canvas: TCanvas);
  end;

procedure TShape.MoveTo(X, Y: Integer);
begin
  FX := X;
  FY := Y;
end;

// usage - reference counted automatically
var
  Shape: IMovable;
begin
  Shape := TShape.Create;          // ref count = 1
  Shape.MoveTo(100, 200);
  ShowMessage(Format('%d, %d', [Shape.Position.X, Shape.Position.Y]));
  // when Shape goes out of scope, ref count drops to 0, object freed
end;

Injection de dépendances avec interfaces

Les interfaces permettent l'injection de dépendances — passez les dépendances (ILogger, IUserDataAccess) via les constructeurs plutôt que de les coder en dur. Cela découple TUserService des implémentations concrètes : remplacez TConsoleLogger par TFileLogger sans modifier TUserService. TUserService lui-même n'est pas à comptage de références (hérite de TObject, pas de TInterfacedObject) donc il a besoin d'un Free manuel. Pour un DI complet, utilisez un conteneur (Spring4D, DSharp) qui résout les dépendances par type d'interface : Container.RegisterType<ILogger, TConsoleLogger> ; Container.Build ; Service := Container.Resolve<TUserService>. Le DI améliore la testabilité (injecter des mocks), la maintenabilité et la modularité. Dépendez toujours des abstractions (interfaces), pas des concrétions.

delphi
type
  ILogger = interface
    procedure Log(const Msg: string);
  end;

  IUserDataAccess = interface
    function GetUser(Id: Integer): string;
  end;

  TConsoleLogger = class(TInterfacedObject, ILogger)
    procedure Log(const Msg: string);
  end;

  TDatabaseAccess = class(TInterfacedObject, IUserDataAccess)
    function GetUser(Id: Integer): string;
  end;

  TUserService = class
  private
    FLogger: ILogger;
    FDataAccess: IUserDataAccess;
  public
    constructor Create(ALogger: ILogger; ADataAccess: IUserDataAccess);
    function GetUserName(Id: Integer): string;
  end;

constructor TUserService.Create(ALogger: ILogger; ADataAccess: IUserDataAccess);
begin
  FLogger := ALogger;
  FDataAccess := ADataAccess;
end;

function TUserService.GetUserName(Id: Integer): string;
begin
  FLogger.Log('Fetching user ' + IntToStr(Id));
  Result := FDataAccess.GetUser(Id);
end;

// wire up dependencies (manual DI)
var
  Logger: ILogger;
  DataAccess: IUserDataAccess;
  Service: TUserService;
begin
  Logger := TConsoleLogger.Create;
  DataAccess := TDatabaseAccess.Create;
  Service := TUserService.Create(Logger, DataAccess);
  try
    ShowMessage(Service.GetUserName(42));
  finally
    Service.Free;    // TUserService is not ref-counted (not TInterfacedObject)
  end;
end;

Interop COM

COM (Component Object Model) permet à Delphi d'interagir avec les applications et bibliothèques Windows. CreateOleObject crée des objets COM via liaison tardive (type Variant — pas de vérification à la compilation, mais simple). Import Type Library génère des unités à liaison anticipée avec des interfaces typées (IntelliSense, vérification de type, meilleures performances). IUnknown est l'interface COM de base avec AddRef/Release/QueryInterface pour le comptage de références. stdcall est la convention d'appel COM. CoCreateInstance est l'API de bas niveau. Utilisations courantes de COM : automatisation Office (Excel, Word), ADO (base de données), intégration shell, requêtes WMI. Appelez toujours CoInitialize avant les opérations COM dans les threads. Les objets COM sont à thread cloisonné — marshalez entre les threads avec soin.

delphi
uses
  Winapi.ActiveX, System.Win.ComObj;

// create COM object (e.g., Excel)
var
  Excel: Variant;
  Workbook: Variant;
  Sheet: Variant;
begin
  Excel := CreateOleObject('Excel.Application');
  try
    Excel.Visible := True;
    Workbook := Excel.Workbooks.Add;
    Sheet := Workbook.Worksheets[1];

    // write data
    Sheet.Cells[1, 1].Value := 'Name';
    Sheet.Cells[1, 2].Value := 'Score';
    Sheet.Cells[2, 1].Value := 'Alice';
    Sheet.Cells[2, 2].Value := 95;

    // formula
    Sheet.Cells[3, 2].Value := '=AVERAGE(B2:B2)';

    Workbook.SaveAs('C:\report.xlsx');
  finally
    Excel.Quit;
  end;
end;

// import type library for early binding
// Component → Import Component → Import Type Library
// generates a unit with typed interfaces (early binding, IntelliSense)

// IUnknown - base COM interface
type
  IMyComObject = interface(IUnknown)
    ['{...}']
    function DoSomething: HResult; stdcall;
  end;

// CoCreateInstance for low-level COM
var
  Obj: IUnknown;
  MyObj: IMyComObject;
begin
  CoCreateInstance(CLASS_MyComObject, nil, CLSCTX_INPROC_SERVER,
    IMyComObject, MyObj);
  MyObj.DoSomething;
end;

Implements & Agrégation

La directive 'implements' délègue une interface à une propriété — composition plutôt qu'héritage. TDataService expose ICache en déléguant à FCache (un TMemoryCache). C'est plus propre que l'héritage et vous permet de mélanger et assortir les comportements. Supports() vérifie si un objet implémente une interface (utilise QueryInterface en interne). L'opérateur As effectue un cast d'interface vérifié. La délégation d'interface permet le modèle décorateur (envelopper un cache avec journalisation), le modèle stratégie (permuter les implémentations de cache) et une séparation claire des préoccupations. QueryInterface de COM est le mécanisme sous-jacent — chaque objet d'interface peut être interrogé pour n'importe quelle interface qu'il prend en charge.

delphi
type
  ICache = interface
    function Get(const Key: string): string;
    procedure Put(const Key, Value: string);
  end;

  TMemoryCache = class(TInterfacedObject, ICache)
  private
    FDict: TDictionary<string, string>;
  public
    constructor Create;
    destructor Destroy; override;
    function Get(const Key: string): string;
    procedure Put(const Key, Value: string);
  end;

  TDataService = class(TInterfacedObject, ICache)
  private
    FCache: ICache;
  public
    constructor Create(ACache: ICache);
    // 'implements' delegates ICache to FCache
    property Cache: ICache read FCache implements ICache;
  end;

// usage - TDataService exposes ICache via delegation
var
  Service: ICache;
begin
  Service := TDataService.Create(TMemoryCache.Create);
  Service.Put('key1', 'value1');    // delegates to TMemoryCache
  ShowMessage(Service.Get('key1'));
end;

// QueryInterface / Supports
var
  Obj: TInterfacedObject;
  Intf: ICache;
begin
  Obj := TMemoryCache.Create;
  if Supports(Obj, ICache, Intf) then
    Intf.Put('a', 'b');
end;

Références faibles & non sûres

Le comptage de références peut causer des fuites de mémoire avec des références circulaires (parent↔enfant). [Weak] casse les cycles — il suit la référence mais n'incrémente pas le compteur de références, et est automatiquement mis à nil lorsque la cible est libérée. [Unsafe] est un pointeur brut (pas de suivi, pas de compteur de références) — le plus rapide mais dangereux (pointeurs pendants). Utilisez [Weak] pour les références parent/arrière, les modèles d'observateur et les abonnements d'événements. La référence par défaut (forte) incrémente le compteur de références et maintient l'objet en vie. L'ARC de Delphi (obsolète au profit de [Weak]) gérait cela automatiquement sur mobile. Sur desktop, les interfaces utilisent le comptage de références manuel — [Weak] est essentiel pour des conceptions sans cycle. Associez toujours correctement les références fortes et faibles.

delphi
type
  TParent = class;
  TChild = class;

  TParent = class(TInterfacedObject)
  private
    FChild: TChild;
    procedure ChildCallback(const Msg: string);
  public
    destructor Destroy; override;
    property Child: TChild read FChild;
  end;

  TChild = class(TInterfacedObject)
  private
    // [Weak] avoids circular reference counting (parent-child cycle)
    [Weak] FParent: TParent;
    FCallback: TProc<string>;
  public
    constructor Create(AParent: TParent);
    property Parent: TParent read FParent;
  end;

  // [Unsafe] - raw pointer, no ref counting at all
  // [Weak] - tracked but doesn't increment ref count
  // (default) - strong reference, increments ref count

destructor TParent.Destroy;
begin
  FChild := nil;  // releases strong ref
  inherited;
end;

// without [Weak], this creates a memory leak:
// Parent holds Child (ref=1), Child holds Parent (ref=1)
// neither ref count ever reaches 0 → leak

var
  Parent: TParent;
begin
  Parent := TParent.Create;
  // when Parent goes out of scope, both are freed correctly
end;
15

Multithreading & PPL

Bases de TThread

TThread est le fondement du multithreading Delphi. Surchargez Execute avec le travail en arrière-plan. Vérifiez Terminated périodiquement pour une annulation gracieuse. TThread.Synchronize exécute du code sur le thread principal (blocant — attend qu'il se termine) ; TThread.Queue est asynchrone (publie et retourne immédiatement). N'accédez JAMAIS aux contrôles d'interface depuis des threads d'arrière-plan — utilisez toujours Synchronize ou Queue. FreeOnTerminate := True libère automatiquement le thread lorsque Execute se termine. CreateAnonymousThread crée un thread unique à partir d'une méthode anonyme — pratique pour les tâches simples. Pour le code de production, préférez PPL (TTask) à TThread brut pour une meilleure composition et une meilleure gestion des erreurs.

delphi
type
  TWorkerThread = class(TThread)
  private
    FResult: Integer;
  protected
    procedure Execute; override;
  public
    property Result: Integer read FResult;
  end;

procedure TWorkerThread.Execute;
var
  I: Integer;
begin
  FResult := 0;
  for I := 1 to 100 do
  begin
    if Terminated then Break;       // check for cancellation
    FResult := FResult + I;
    Sleep(10);                      // simulate work
  end;

  // update UI from background thread
  TThread.Synchronize(nil,
    procedure
    begin
      Form1.Label1.Caption := 'Done: ' + IntToStr(FResult);
    end);
end;

// create and run
var
  Worker: TWorkerThread;
begin
  Worker := TWorkerThread.Create(True);  // suspended
  Worker.FreeOnTerminate := True;        // auto-free when done
  Worker.Start;                          // begin execution
end;

// TThread.CreateAnonymousThread - quick one-off
TThread.CreateAnonymousThread(
  procedure
  var I: Integer;
  begin
    for I := 1 to 10 do
      TThread.Queue(nil,
        procedure
        begin
          Form1.Label1.Caption := IntToStr(I);
        end);
  end).Start;

Bibliothèque de programmation parallèle (PPL)

La Bibliothèque de Programmation Parallèle (PPL) dans System.Threading fournit une concurrence de haut niveau : TTask (async fire-and-forget), TTask.Future<T> (async avec valeur de retour) et boucles parallèles. Les tâches utilisent le pool de threads automatiquement — pas besoin de gérer les threads. WaitForAll/WaitForAny composent plusieurs tâches. Future.Value bloque jusqu'à ce que le résultat soit prêt (comme une promesse). PPL est l'alternative moderne à TThread brut — plus propre, composable et s'intègre avec les modèles async/await. Les tâches capturent les exceptions et les relèvent lorsque vous accédez à .Value, permettant une propagation correcte des erreurs. Utilisez TEvent/TCountdownEvent pour une synchronisation fine entre les tâches.

delphi
uses System.Threading, System.SyncObjs;

// TTask - async operations
var
  Task: ITask;
begin
  Task := TTask.Create(
    procedure
    begin
      Sleep(2000);  // simulate work
      TThread.Queue(nil,
        procedure
        begin
          ShowMessage('Task done');
        end);
    end);
  Task.Start;
end;

// TTask.WaitForAll - wait for multiple tasks
var
  Tasks: array of ITask;
begin
  SetLength(Tasks, 3);
  Tasks[0] := TTask.Create(procedure begin DownloadFile('a.txt'); end);
  Tasks[1] := TTask.Create(procedure begin DownloadFile('b.txt'); end);
  Tasks[2] := TTask.Create(procedure begin DownloadFile('c.txt'); end);

  for var T in Tasks do T.Start;

  // wait for all (with timeout)
  if TTask.WaitForAll(Tasks, 30000) then
    ShowMessage('All downloads complete')
  else
    ShowMessage('Timeout');
end;

// TTask.Future<T> - async with return value
var
  Future: IFuture<string>;
begin
  Future := TTask.Future<string>(
    function: string
    begin
      Result := FetchDataFromServer;  // long operation
    end);

  // do other work...
  ShowMessage('Result: ' + Future.Value);  // blocks until ready
end;

Parallel For & Boucles

TParallel.For parallélise les boucles sur le pool de threads — les itérations s'exécutent simultanément sur plusieurs cœurs. Utilisez &For (mot-clé échappé) puisque 'for' est réservé. Pour les boucles liées au CPU avec des itérations indépendantes, cela peut donner une accélération quasi-linéaire sur les machines multicœurs. CRITIQUE : l'état partagé (comme Sum) doit être protégé par des verrous (TCriticalSection) ou utiliser TInterlocked.Increment pour les opérations atomiques. State.Break arrête la boucle (comme break). State.ShouldExit vérifie si Break a été appelé. Évitez de paralléliser les boucles avec peu d'itérations ou des E/S lourdes (le pool de threads est épuisé). Stride contrôle le pas d'itération. Les boucles parallèles imbriquées aident rarement — la boucle externe sature déjà les cœurs.

delphi
uses System.Threading, System.SyncObjs;

// TParallel.For - parallelized loop
var
  Sum: Integer;
  Lock: TCriticalSection;
  I: Integer;
begin
  Sum := 0;
  Lock := TCriticalSection.Create;
  try
    TParallel.&For(1, 1000000,
      procedure(Index: Integer)
      begin
        // thread-safe accumulation
        Lock.Enter;
        try
          Sum := Sum + Index;
        finally
          Lock.Leave;
        end;
      end);
    ShowMessage('Sum: ' + IntToStr(Sum));
  finally
    Lock.Free;
  end;
end;

// with stride and state
TParallel.&For(1, 100,
  procedure(Index: Integer; var State: TParallelLoopState)
  begin
    if Index = 50 then
      State.Break;                  // stop after current iterations
    // process Index...
  end);

// TParallel.For with step (stride)
TParallel.&For(0, 99, 2,    // 0, 2, 4, 6, ...
  procedure(Index: Integer)
  begin
    ProcessEven(Index);
  end);

// nested parallel loops (use sparingly)
TParallel.&For(0, 9,
  procedure(I: Integer)
  begin
    TParallel.&For(0, 9,
      procedure(J: Integer)
      begin
        Matrix[I, J] := Compute(I, J);
      end);
  end);

Primitives de synchronisation

System.SyncObjs fournit des primitives de synchronisation : TCriticalSection (mutex — un seul thread à la fois), TEvent (signaler entre threads — SetEvent réveille, WaitFor bloque), TMonitor (verrouiller n'importe quel objet — comme les moniteurs Java/C# avec Wait/Pulse), TInterlocked (Increment/Decrement/Exchange/CompareExchange atomiques — sans verrou). TCriticalSection est le plus courant — associez toujours Enter/Leave avec try/finally. TEvent.WaitFor retourne wrSignaled, wrTimeout ou wrAbandoned. TMonitor.Wait libère temporairement le verrou et bloque ; Pulse/PulseAll réveillent les attendeurs. TInterlocked est le plus rapide pour les compteurs simples — pas de surcoût de verrou. Choisissez la bonne primitive : CriticalSection pour l'accès exclusif, Event pour la signalisation, Interlocked pour les compteurs atomiques.

delphi
uses System.SyncObjs;

// TCriticalSection - mutual exclusion
var
  CS: TCriticalSection;
begin
  CS := TCriticalSection.Create;
  try
    CS.Enter;
    try
      // exclusive access to shared data
    finally
      CS.Leave;
    end;
  finally
    CS.Free;
  end;
end;

// TEvent - signaling between threads
var
  Event: TEvent;
begin
  Event := TEvent.Create(nil, True, False, '');  // manual reset
  try
    // thread 1: wait
    if Event.WaitFor(5000) = wrSignaled then
      ShowMessage('Signaled');

    // thread 2: signal
    Event.SetEvent;    // wake waiting threads
    Event.ResetEvent;  // clear signal
  finally
    Event.Free;
  end;
end;

// TMonitor - lock any object
var
  List: TList<Integer>;
begin
  TMonitor.Enter(List);
  try
    List.Add(42);
  finally
    TMonitor.Exit(List);
  end;

  // TMonitor.Wait / Pulse (like Java wait/notify)
  TMonitor.Enter(List);
  try
    while List.Count = 0 do
      TMonitor.Wait(List, 1000);  // release lock, wait
    TMonitor.PulseAll(List);      // wake waiting threads
  finally
    TMonitor.Exit(List);
  end;
end;

// TInterlocked - atomic operations
TInterlocked.Increment(Counter);
TInterlocked.Exchange(Value, 42);

Pool de threads & Modèle Async/Await

TThreadPool gère un pool de threads de travail — la réutilisation des threads évite le surcoût de création. Définissez min/max threads selon votre charge de travail (lié au CPU : ~nombre de cœurs, lié aux E/S : plus). TTask.Run est un raccourci pour Create+Start. ContinueWith chaîne les tâches — s'exécute après que l'antécédent se termine, permettant des pipelines. Task.Status (Created, WaitingToRun, Running, Completed, Canceled, Faulted) suit le cycle de vie. ICancellation permet une annulation coopérative — vérifiez IsCancelled périodiquement dans les tâches longues. Pour un véritable async/await, Delphi n'a pas d'await au niveau du langage, mais TTask.Future + .Value fournit une sémantique équivalente. OmniThreadLibrary (OTL) offre des abstractions de plus haut niveau (pipelines, passage de messages) construites au-dessus de PPL.

delphi
uses System.Threading, System.SyncObjs;

// configure thread pool
var
  Pool: TThreadPool;
begin
  Pool := TThreadPool.Create;
  try
    Pool.SetMaxWorkerThreads(8);
    Pool.SetMinWorkerThreads(2);

    // use pool with TTask
    TTask.Run(
      procedure
      begin
        // runs on the pool
      end, Pool);
  finally
    Pool.Free;
  end;
end;

// async/await pattern using futures
function FetchDataAsync: IFuture<TStrings>;
begin
  Result := TTask.Future<TStrings>(
    function: TStrings
    begin
      Result := TStringList.Create;
      // simulate slow fetch
      TThread.Sleep(2000);
      Result.LoadFromFile('data.txt');
    end);
end;

// chain tasks
var
  Task1, Task2: ITask;
begin
  Task1 := TTask.Run(
    procedure
    begin
      DownloadFile('part1.zip');
    end);

  // Task2 runs after Task1 completes
  Task2 := Task1.ContinueWith(
    procedure(const ATask: ITask)
    begin
      if ATask.Status = TTaskStatus.Completed then
        ProcessFile('part1.zip')
      else
        ShowMessage('Download failed');
    end);
end;

// cancellation
var
  Cancel: ICancellation;
begin
  Cancel := TTask.CurrentTask.Cancellation;
  while not Cancel.IsCancelled do
  begin
    DoChunk;
    Sleep(100);
  end;
end;
16

Programmation réseau avec Indy

Client & Serveur TCP (Indy)

Indy (Internet Direct) est la bibliothèque réseau intégrée de Delphi. TIdTCPClient se connecte aux serveurs — WriteLn/ReadLn pour les protocoles basés sur des lignes, Write/Read pour le binaire. ConnectTimeout empêche le blocage. TIdTCPServer écoute les connexions — OnExecute s'exécute dans un thread par client (AContext représente chaque connexion). Indy utilise des sockets bloquants (modèle plus simple — pas de rappels), donc les gestionnaires serveur s'exécutent dans des threads de travail. Gérez toujours les déconnexions gracieusement. Pour les serveurs haute performance, envisagez ICS (E/S avec chevauchement) ou Synapse. Les composants Indy sont non visuels — déposez sur un formulaire ou créez dans le code. Définissez Active := True pour commencer à écouter. DefaultPort définit le port d'écoute.

delphi
uses IdTCPClient, IdTCPServer, IdContext;

// TCP Client
var
  Client: TIdTCPClient;
  Response: string;
begin
  Client := TIdTCPClient.Create(nil);
  try
    Client.Host := 'example.com';
    Client.Port := 8080;
    Client.ConnectTimeout := 5000;
    Client.Connect;
    try
      Client.IOHandler.WriteLn('Hello Server');
      Response := Client.IOHandler.ReadLn;
      ShowMessage('Server: ' + Response);
    finally
      Client.Disconnect;
    end;
  finally
    Client.Free;
  end;
end;

// TCP Server
type
  TForm1 = class(TForm)
    IdTCPServer1: TIdTCPServer;
    procedure FormCreate(Sender: TObject);
    procedure ServerExecute(AContext: TIdContext);
  end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  IdTCPServer1.DefaultPort := 8080;
  IdTCPServer1.OnExecute := ServerExecute;
  IdTCPServer1.Active := True;
end;

procedure TForm1.ServerExecute(AContext: TIdContext);
var
  Msg: string;
begin
  Msg := AContext.Connection.IOHandler.ReadLn;
  AContext.Connection.IOHandler.WriteLn('Echo: ' + Msg);
  if Msg = 'quit' then
    AContext.Connection.Disconnect;
end;

Client HTTP (TIdHTTP)

TIdHTTP est le client HTTP d'Indy — prend en charge GET, POST, PUT, DELETE, en-têtes, cookies et SSL/TLS. Pour HTTPS, attachez TIdSSLIOHandlerSocketOpenSSL (nécessite les DLLs OpenSSL : libeay32/ssleay32 ou libcrypto/libssl). Request.ContentType et CustomHeaders définissent les métadonnées de requête. POST accepte un corps de chaîne (pour JSON/APIs) ou TStrings (pour les données de formulaire). EIdHTTPProtocolException attrape les erreurs HTTP (404, 500, etc.) avec ErrorCode et ErrorMessage. Pour les clients REST modernes, envisagez TRESTClient (intégré, pas de dépendance OpenSSL) ou TNetHTTPClient (plus léger). Libérez toujours HTTP et le gestionnaire SSL dans des blocs finally. Définissez Http.HandleRedirects := True pour suivre automatiquement les redirections 301/302.

delphi
uses IdHTTP, IdSSLOpenSSL, System.JSON;

var
  Http: TIdHTTP;
  SSL: TIdSSLIOHandlerSocketOpenSSL;
  Response: string;
  JSON: TJSONObject;
  Params: TStringList;
begin
  Http := TIdHTTP.Create(nil);
  SSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
  try
    Http.IOHandler := SSL;
    SSL.SSLOptions.Method := sslvTLSv1_2;
    Http.Request.ContentType := 'application/json';
    Http.Request.CustomHeaders.AddValue('Authorization', 'Bearer token123');

    // GET request
    Response := Http.Get('https://api.example.com/users');
    ShowMessage(Response);

    // POST with JSON body
    JSON := TJSONObject.Create;
    try
      JSON.AddPair('name', 'Alice');
      JSON.AddPair('age', 30);
      Response := Http.Post('https://api.example.com/users', JSON.ToJSON);
    finally
      JSON.Free;
    end;

    // POST form data
    Params := TStringList.Create;
    try
      Params.Add('username=alice');
      Params.Add('password=secret');
      Response := Http.Post('https://api.example.com/login', Params);
    finally
      Params.Free;
    end;

    // handle errors
    try
      Http.Get('https://api.example.com/missing');
    except
      on E: EIdHTTPProtocolException do
        ShowMessage('HTTP ' + IntToStr(E.ErrorCode) + ': ' + E.ErrorMessage);
    end;
  finally
    SSL.Free;
    Http.Free;
  end;
end;

Email SMTP (TIdSMTP)

TIdSMTP envoie des emails via des serveurs SMTP. TIdMessage représente l'email (From, Recipients, Subject, Body). Pour Gmail/Office365, utilisez TLS (Port 587, utUseExplicitTLS) ou SSL (Port 465, utUseImplicitTLS). Gmail nécessite un 'Mot de passe d'application' (pas votre mot de passe habituel) avec 2FA activé. TIdAttachmentFile ajoute des pièces jointes. Pour les emails HTML, définissez ContentType := 'text/html'. Pour multipart (HTML + texte brut + pièces jointes), utilisez TIdMessageBuilderHTML. Ports courants : 25 (non chiffré/relais), 465 (SSL), 587 (STARTTLS). Enveloppez toujours Connect/Send dans try/finally pour garantir Disconnect. Pour recevoir des emails, utilisez TIdPOP3 ou TIdIMAP4.

delphi
uses IdSMTP, IdMessage, IdSSLOpenSSL, IdExplicitTLSClientServerBase;

var
  SMTP: TIdSMTP;
  Msg: TIdMessage;
  SSL: TIdSSLIOHandlerSocketOpenSSL;
begin
  SMTP := TIdSMTP.Create(nil);
  Msg := TIdMessage.Create(nil);
  SSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
  try
    // SMTP config (Gmail example)
    SMTP.Host := 'smtp.gmail.com';
    SMTP.Port := 587;
    SMTP.UseTLS := utUseExplicitTLS;
    SMTP.IOHandler := SSL;
    SSL.SSLOptions.Method := sslvTLSv1_2;
    SMTP.Username := '[email protected]';
    SMTP.Password := 'app-password';

    // message
    Msg.From.Address := '[email protected]';
    Msg.From.Name := 'My App';
    Msg.Recipients.Add.Address := '[email protected]';
    Msg.Subject := 'Test from Delphi';
    Msg.Body.Text := 'Hello,' + sLineBreak + 'This is a test email.';

    // attachment
    var Attachment := TIdAttachmentFile.Create(Msg.MessageParts,
      'C:\report.pdf');

    // HTML body
    Msg.ContentType := 'text/html';
    Msg.Body.Text := '<h1>Hello</h1><p>HTML email from Delphi</p>';

    // connect and send
    SMTP.Connect;
    try
      SMTP.Send(Msg);
      ShowMessage('Email sent!');
    finally
      SMTP.Disconnect;
    end;
  finally
    SSL.Free;
    Msg.Free;
    SMTP.Free;
  end;
end;

UDP & Sockets bruts

UDP est sans connexion — pas de handshake, pas de livraison garantie, mais plus rapide que TCP. TIdUDPClient.Send envoie des datagrammes ; ReceiveString attend les réponses avec timeout. BroadcastEnabled envoie à 255.255.255.255 (tous les appareils sur le LAN) — utile pour la découverte de services. TIdUDPServer.OnUDPRead reçoit les datagrammes ; ABinding.PeerIP/PeerPort identifient l'expéditeur. UDP est idéal pour : DNS, SNMP, mises à jour d'état de jeu, streaming média et protocoles de découverte. Pour la fiabilité sur UDP, implémentez ACK/réessai au niveau de l'application. TIdBytes est le type de tableau d'octets d'Indy — utilisez BytesToString/ToBytes pour la conversion. Pour le contrôle de socket brut (paquets IP bruts, protocoles personnalisés), utilisez l'unité WinSock2 ou la bibliothèque Synapse.

delphi
uses IdUDPClient, IdUDPServer, IdSocketHandle;

// UDP Client (connectionless, fire-and-forget)
var
  UDP: TIdUDPClient;
begin
  UDP := TIdUDPClient.Create(nil);
  try
    UDP.Host := '255.255.255.255';  // broadcast
    UDP.Port := 9999;
    UDP.BroadcastEnabled := True;
    UDP.Send('DISCOVER');

    // receive response
    var Response: string;
    UDP.ReceiveString(Response, 1000);  // timeout 1s
    ShowMessage(Response);
  finally
    UDP.Free;
  end;
end;

// UDP Server
type
  TForm1 = class(TForm)
    IdUDPServer1: TIdUDPServer;
    procedure FormCreate(Sender: TObject);
    procedure UDPRead(AThread: TIdUDPListenerThread;
      const AData: TIdBytes; ABinding: TIdSocketHandle);
  end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  IdUDPServer1.DefaultPort := 9999;
  IdUDPServer1.OnUDPRead := UDPRead;
  IdUDPServer1.Active := True;
end;

procedure TForm1.UDPRead(AThread: TIdUDPListenerThread;
  const AData: TIdBytes; ABinding: TIdSocketHandle);
var
  Msg: string;
begin
  Msg := BytesToString(AData);
  // reply to sender
  ABinding.SendTo(ABinding.PeerIP, ABinding.PeerPort,
    ToBytes('ACK: ' + Msg));
end;

// raw socket with TIdIOHandlerSocket
// for low-level protocols, use WinSock2 unit directly

FTP & Client REST

TIdFTP fournit la fonctionnalité de client FTP — Connect, List, Put (téléverser), Get (télécharger), MakeDir, ChangeDir. Le mode passif (Passive := True) traverse les NAT/pare-feu. UseTLS sécurise le FTP (FTPS). Pour SFTP (basé sur SSH), utilisez une bibliothèque tierce (libssh2, SecureBlackbox). TRESTClient/TRESTRequest/TRESTResponse sont des composants REST intégrés (pas de dépendance OpenSSL) — idéaux pour la consommation d'API moderne. Resource utilise des espaces réservés {param} remplis par AddUrlSegment. Execute envoie la requête ; RESTResponse.Content contient le corps ; JSONValue analyse le JSON automatiquement. Les composants REST prennent en charge OAuth2, l'authentification de base et les authentificateurs personnalisés. Pour le REST haute performance, envisagez TNetHTTPClient (plus léger) ou TIdHTTP d'Indy pour un contrôle maximal.

delphi
uses IdFTP, IdFTPCommon, IdExplicitTLSClientServerBase;

// FTP client
var
  FTP: TIdFTP;
begin
  FTP := TIdFTP.Create(nil);
  try
    FTP.Host := 'ftp.example.com';
    FTP.Username := 'user';
    FTP.Password := 'pass';
    FTP.Passive := True;            // NAT-friendly mode
    FTP.UseTLS := utUseExplicitTLS;
    FTP.Connect;
    try
      // list directory
      var Listing: TStringList := TStringList.Create;
      try
        FTP.List(Listing);
        for var S in Listing do
          ShowMessage(S);
      finally
        Listing.Free;
      end;

      // change directory
      FTP.ChangeDir('/uploads');

      // upload/download
      FTP.Put('C:\local.txt', 'remote.txt');
      FTP.Get('remote.txt', 'C:\downloaded.txt');

      // create/remove directory
      FTP.MakeDir('newfolder');
      FTP.RemoveDir('oldfolder');
    finally
      FTP.Disconnect;
    end;
  finally
    FTP.Free;
  end;
end;

// REST Client (built-in, no Indy needed)
uses REST.Client, REST.Types;

var
  RESTClient: TRESTClient;
  RESTRequest: TRESTRequest;
  RESTResponse: TRESTResponse;
begin
  RESTClient := TRESTClient.Create('https://api.example.com');
  RESTRequest := TRESTRequest.Create(RESTClient);
  RESTResponse := TRESTResponse.Create(nil);
  try
    RESTRequest.Resource := 'users/{id}';
    RESTRequest.Method := TRESTRequestMethod.rmGET;
    RESTRequest.Params.AddUrlSegment('id', '42');
    RESTRequest.Params.AddItem('fields', 'name,email', pkGETorPOST);

    RESTRequest.Execute;
    ShowMessage(RESTResponse.Content);     // JSON response

    // access JSON fields directly
    ShowMessage(RESTResponse.JSONValue.GetValue<string>('name'));
  finally
    RESTResponse.Free;
    RESTRequest.Free;
    RESTClient.Free;
  end;
end;
17

Packages DLL & BPL

Création & Utilisation de DLLs

Les DLLs (Dynamic Link Libraries) partagent du code entre applications. Utilisez le mot-clé 'library' (pas 'program') pour construire une DLL. 'exports' liste les fonctions disponibles pour les appelants externes. stdcall est la convention d'appel Windows standard (compatible C/C++, VB, C#). L'import statique (external) lie à la compilation — la DLL doit exister à l'exécution. Le chargement dynamique (LoadLibrary/GetProcAddress) charge à l'exécution — permet les plugins et les fonctionnalités optionnelles. FreeLibrary décharge la DLL. PChar (PWideChar) est le type de chaîne standard pour les exports DLL (mémoire partagée, pas de types spécifiques à Delphi). N'exportez JAMAIS de chaînes, objets ou interfaces Delphi directement — ils sont internes à Delphi. Utilisez l'unité ShareMem pour le partage de chaînes Delphi-vers-Delphi (nécessite BorlndMM.dll).

delphi
// --- MyLib.dpr (DLL project) ---
library MyLib;

uses
  System.SysUtils, System.Classes;

// exported function (stdcall for compatibility)
function Add(A, B: Integer): Integer; stdcall;
begin
  Result := A + B;
end;

// exported procedure
procedure ShowMessage(const Msg: PChar); stdcall;
begin
  WriteLn(Msg);
end;

// export table
exports
  Add name 'Add',
  ShowMessage name 'ShowMessage';

begin
end.

// --- MainApp.dpr (consumer) ---
// static import
function Add(A, B: Integer): Integer; stdcall; external 'MyLib.dll';
procedure ShowMsg(const Msg: PChar); stdcall; external 'MyLib.dll';

begin
  ShowMessage(IntToStr(Add(3, 4)));  // 7
end;

// dynamic loading (load at runtime)
var
  LibHandle: THandle;
  AddFunc: function(A, B: Integer): Integer; stdcall;
begin
  LibHandle := LoadLibrary('MyLib.dll');
  if LibHandle <> 0 then
  try
    @AddFunc := GetProcAddress(LibHandle, 'Add');
    if Assigned(@AddFunc) then
      ShowMessage(IntToStr(AddFunc(10, 20)));
  finally
    FreeLibrary(LibHandle);
  end;
end;

Partage d'objets via interfaces

Partager des objets à travers les frontières DLL est délicat — les classes Delphi ne peuvent pas être exportées directement (gestionnaires de mémoire différents, RTTI différents). La solution : utilisez des interfaces avec GUIDs. La DLL exporte une fonction de fabrique (CreatePlugin) qui retourne un IPlugin. L'application hôte définit la même interface (même GUID !) et appelle la fabrique. Le comptage de références d'interface gère le nettoyage automatiquement. Utilisez PChar pour les chaînes (pas string Delphi) pour éviter les conflits de gestionnaire de mémoire. C'est le modèle d'architecture de plugin — chargez les DLLs dynamiquement, créez les plugins via la fabrique, communiquez via les interfaces. Pour les systèmes de plugins complets, envisagez le framework de plugins dans Delphi ou utilisez les packages (BPL) qui partagent la RTL et permettent le partage direct de classes.

delphi
// --- PluginDLL.dpr ---
library PluginDLL;

type
  IPlugin = interface
    ['{12345678-1234-1234-1234-123456789012}']
    function GetName: PChar; stdcall;
    function Execute(const Input: PChar): PChar; stdcall;
    procedure Free; stdcall;
  end;

  TMyPlugin = class(TInterfacedObject, IPlugin)
  public
    function GetName: PChar; stdcall;
    function Execute(const Input: PChar): PChar; stdcall;
    procedure Free; stdcall;
  end;

function TMyPlugin.GetName: PChar;
begin
  Result := 'My Plugin v1.0';
end;

function TMyPlugin.Execute(const Input: PChar): PChar;
begin
  Result := PChar('Processed: ' + Input);
end;

// factory function - creates and returns the plugin
function CreatePlugin: IPlugin; stdcall;
begin
  Result := TMyPlugin.Create;
end;

exports CreatePlugin;

// --- HostApp.dpr ---
type
  IPlugin = interface
    ['{12345678-1234-1234-1234-123456789012}']
    function GetName: PChar; stdcall;
    function Execute(const Input: PChar): PChar; stdcall;
    procedure Free; stdcall;
  end;

var
  CreatePlugin: function: IPlugin; stdcall;
  Plugin: IPlugin;
  Handle: THandle;
begin
  Handle := LoadLibrary('PluginDLL.dll');
  if Handle <> 0 then
  try
    @CreatePlugin := GetProcAddress(Handle, 'CreatePlugin');
    if Assigned(@CreatePlugin) then
    begin
      Plugin := CreatePlugin;
      ShowMessage(Plugin.GetName);
      ShowMessage(Plugin.Execute('test'));
    end;
  finally
    FreeLibrary(Handle);
  end;
end;

Packages BPL (Borland Package Library)

Les BPLs (Borland Package Libraries) sont des bibliothèques partagées spécifiques à Delphi — elles partagent la RTL Delphi, permettant le partage direct de classes/objets (contrairement aux DLLs). Construisez avec le mot-clé 'package'. Les packages d'exécution réduisent la taille de l'EXE (code partagé dans les fichiers .bpl) et permettent des modules interchangeables à chaud. LoadPackage/UnloadPackage chargent les BPLs dynamiquement — GetClass trouve les classes enregistrées par nom. RegisterClass/UnRegisterClass rendent les classes découvrables. Les BPLs nécessitent que les BPLs RTL Delphi (rtl.bpl, vcl.bpl) soient déployées. Utilisez les BPLs pour : architectures de plugins (partagez les types Delphi directement), applications modulaires (chargez les fonctionnalités à la demande) et réduction de la mémoire (code partagé chargé une fois). Pour le partage inter-langage, utilisez les DLLs ; pour Delphi uniquement, les BPLs sont plus puissantes.

delphi
// --- MyPackage.dpk (runtime package) ---
package MyPackage;

requires
  rtl,
  vcl;

contains
  MyUnit in 'MyUnit.pas',
  MyForm in 'MyForm.pas' {Form1};

// compile: dcc32 -B MyPackage.dpk
// produces MyPackage.bpl (shared runtime package)

// --- Using the package ---
// Option 1: link statically (compile-time reference)
// Project → Options → Packages → Runtime packages → add MyPackage.bpl

// Option 2: load dynamically with LoadPackage
var
  PackageModule: THandle;
  FormClass: TPersistentClass;
begin
  PackageModule := LoadPackage('MyPackage.bpl');
  try
    // register and use forms/classes from the package
    FormClass := GetClass('TForm1');
    if FormClass <> nil then
      with TFormClass(FormClass).Create(Application) do
        try
          ShowModal;
        finally
          Free;
        end;
  finally
    UnloadPackage(PackageModule);
  end;
end;

// RegisterClass in the package's unit:
unit MyForm;
interface
uses Vcl.Forms;
type
  TForm1 = class(TForm)
  end;
implementation
initialization
  RegisterClass(TForm1);    // make class discoverable
finalization
  UnRegisterClass(TForm1);
end.

Gestion de la mémoire à travers les frontières

Le piège n°1 des DLL : libérer de la mémoire dans un module qui a été allouée dans un autre. Chaque module a son propre gestionnaire de mémoire — les mélanger cause une corruption du tas et des crashes. Solutions : (1) ShareMem — partage BorlndMM.dll, mais nécessite le déploiement de cette DLL. (2) Modèle appelant-alloue — l'appelant fournit le tampon, la DLL le remplit (le plus sûr, indépendant du langage). (3) SimpleShareMem/FastMM — gestionnaire de mémoire partagée moderne (FastMM est par défaut depuis Delphi 2006). (4) Libération basée sur rappel — la DLL fournit une fonction free. Pour les retours PChar, utilisez StrNew/StrDispose (API Windows, partagé). Pour la production Delphi-vers-Delphi, utilisez les BPLs (RTL partagée) ou SimpleShareMem. Pour inter-langage, utilisez toujours le modèle appelant-alloue. Ne passez jamais de types string/object/interface Delphi à travers les frontières DLL sans gestionnaire de mémoire partagé.

delphi
// PROBLEM: different memory managers in EXE and DLL
// → crashes when freeing memory allocated in another module

// Solution 1: ShareMem (Delphi-to-Delphi only)
// First unit in both EXE and DLL .dpr file:
uses
  ShareMem;  // uses BorlndMM.dll as shared memory manager

// Solution 2: Caller allocates, caller frees (safest)
// DLL fills a buffer provided by the caller
procedure GetData(Buffer: PChar; var BufSize: Integer); stdcall;
var
  Data: string;
begin
  Data := 'Hello from DLL';
  BufSize := Length(Data) + 1;
  if Buffer <> nil then
    StrLCopy(Buffer, PChar(Data), BufSize);
end;

// caller:
var
  Size: Integer;
  Buffer: PChar;
begin
  GetData(nil, Size);              // query size
  GetMem(Buffer, Size);            // allocate
  try
    GetData(Buffer, Size);         // fill
    ShowMessage(Buffer);
  finally
    FreeMem(Buffer);               // caller frees
  end;
end;

// Solution 3: Use SafeCall / COM-style allocation
// Solution 4: Use FastMM as shared manager (modern approach)
// Add SimpleShareMem unit (uses FastMM) to both projects

// Solution 5: Return only simple types / PChar with callback
type
  TFreeCallback = procedure(Ptr: Pointer); stdcall;
function CreateString(out S: PChar; FreeProc: TFreeCallback): Boolean; stdcall;
begin
  S := StrNew('Allocated in DLL');
  Result := True;
  // caller calls FreeProc(S) which calls StrDispose in the DLL
end;

Fichiers de ressources & Intégration

Les fichiers de ressources intègrent des données binaires (images, icônes, sons, chaînes, infos de version) dans l'EXE/DLL — pas de fichiers externes nécessaires. Créez un script .rc, compilez avec brcc32 (ou laissez l'IDE auto-compiler). {$R file.res} le lie. TResourceStream lit les ressources RCDATA comme un flux. LoadIcon/LoadString utilisent l'API Windows pour des types de ressources spécifiques. Les ressources sont en lecture seule à l'exécution mais gardent tout dans un fichier (excellent pour le déploiement). Utilisations courantes : icônes d'application, images d'écran de démarrage, configuration par défaut, sons WAV, infos de version (boîte de dialogue des propriétés du fichier), chaînes localisées. Pour les grandes données, envisagez la compression avant l'intégration. Les IDs de ressource peuvent être des noms (chaînes) ou des nombres. RT_RCDATA est le type de ressource binaire générique.

delphi
// --- Resource script (.rc file) ---
// MyResources.rc:
//   LOGO     RCDATA "logo.png"
//   ICON1    ICON    "app.ico"
//   VERSION  VERSIONINFO ...
//   WAVE1    WAVE    "sound.wav"
//   STR1     STRINGTABLE { "Hello" }

// compile: brcc32 MyResources.rc → MyResources.res
// or add .rc to project (auto-compiled)

// --- In .dpr ---
{$R MyResources.res}  // link resource

// --- Loading resources ---
uses System.Classes, Vcl.Graphics, Winapi.Windows;

// load RCDATA (binary data)
var
  Stream: TResourceStream;
begin
  Stream := TResourceStream.Create(HInstance, 'LOGO', RT_RCDATA);
  try
    Image1.Picture.LoadFromStream(Stream);
  finally
    Stream.Free;
  end;
end;

// load icon
var
  Icon: TIcon;
begin
  Icon := TIcon.Create;
  try
    Icon.Handle := LoadIcon(HInstance, 'ICON1');
    Image1.Picture.Icon.Assign(Icon);
  finally
    Icon.Free;
  end;
end;

// load string resource
var
  S: string;
  Buffer: array[0..255] of Char;
begin
  LoadString(HInstance, 1, Buffer, SizeOf(Buffer));
  S := Buffer;
end;

// embed a file as resource at compile time
// {$R 'data.bin' 'data.bin'}  // or use .rc
18

Débogage & Optimisation des performances

Débogueur & Points d'arrêt

Le débogueur IDE de Delphi est puissant : définissez des points d'arrêt en cliquant la gouttière. Les points d'arrêt conditionnels s'arrêtent uniquement lorsqu'une expression est vraie (par ex., i > 100). Les points d'arrêt de journal/trace journalisent des messages sans s'arrêter — excellents pour surveiller les boucles. asm int 3 end crée un point d'arrêt matériel dans le code (trap CPU). OutputDebugString journalise vers la fenêtre du Journal d'événements (et l'outil DebugView). Assert vérifie les conditions dans les builds de débogage (désactivé avec {$C-} ou assertions désactivées en release). DebugHook est non nul lors de l'exécution dans l'IDE. La fenêtre Pile des appels trace la chaîne d'appels ; la fenêtre Threads inspecte tous les threads ; Variables locales montre la portée courante. Activez 'Use Debug DCUs' pour entrer dans le code source RTL/VCL.

delphi
// Conditional breakpoints (set in IDE):
//   Break when expression is true
//   e.g., (i > 100) and (List.Count > 0)

// Log breakpoints (no break, just log):
//   Log message: "Iteration {i}, Count={List.Count}"

// Trace points / Action breakpoints:
//   Run macro or evaluate expression on hit

// Code-based breakpoints:
var
  I: Integer;
begin
  for I := 1 to 1000 do
  begin
    // break only when condition met
    if (I mod 100 = 0) and DebugHook <> 0 then
      asm int 3 end;            // hard breakpoint (CPU trap)

    // or use OutputDebugString for logging
    OutputDebugString(PChar('Processing ' + IntToStr(I)));
  end;
end;

// Assert (only in debug builds)
Assert(List.Count > 0, 'List must not be empty');

// DebugHook: 0 = release, 1 = IDE, 2 = IDE step-over
if DebugHook <> 0 then
  ShowMessage('Running in debugger');

// Watch and Evaluate expressions in IDE:
//   List.Count
//   List[0].Name
//   TMyObject(Obj).PrivateField  (with "Use Debug DCUs")

// Call Stack window shows the call chain
// Threads window shows all active threads
// Local Variables shows current scope variables

Gestion des exceptions & Traces de pile

Exceptions Delphi : try/except attrape les erreurs, try/finally garantit le nettoyage. Les classes d'exception forment une hiérarchie : Exception → EDivByZero, EAccessViolation, EListError, EAbort (silencieux), etc. 'on E: ExceptionType do' attrape des types spécifiques ; le 'on E: Exception do' de base attrape tout. 'raise;' relance l'exception courante (préserve la trace de pile). EAbort (ou procédure Abort) lève une exception silencieuse (pas de dialogue). TApplicationEvents.OnException est le gestionnaire global — attrape les exceptions non gérées. Pour les traces de pile, utilisez JCL (JclDebug) ou MadExcept/ExceptionHunter — ils capturent les piles d'appels, les vidages de registres et envoient même des rapports de crash par email. Journalisez toujours les exceptions pour le débogage post-mortem. N'avalez jamais silencieusement les exceptions en production.

delphi
uses
  System.SysUtils, System.Diagnostics;

// structured exception handling
try
  RiskyOperation;
except
  on E: EDivByZero do
    ShowMessage('Division error: ' + E.Message);
  on E: EAccessViolation do
    ShowMessage('Access violation at ' + E.Message);
  on E: Exception do
  begin
    ShowMessage('Unexpected: ' + E.ClassName + ': ' + E.Message);
    raise;                         // re-raise
  end;
end;

// finally (always executes)
try
  AcquireResource;
  UseResource;
finally
  ReleaseResource;                 // always runs
end;

// nested try/except/finally
try
  try
    RiskyCode;
  except
    on E: Exception do
    begin
      LogError(E);
      raise EAbort.Create('');     // suppress display
    end;
  end;
finally
  Cleanup;
end;

// global exception handler
procedure TForm1.ApplicationEvents1Exception(Sender: TObject; E: Exception);
begin
  LogError(Format('%s: %s', [E.ClassName, E.Message]));
  if not (E is EAbort) then
    ShowMessage('Error: ' + E.Message);
end;

// get stack trace (with JCL or MadExcept)
// JclDebug: JclCreateStackInfo, JclLastExceptStackList

Profilage & Performance

TStopwatch est le minuteur haute précision (utilise QueryPerformanceCounter). Benchmarkz toujours avant d'optimiser — ne devinez pas. ReportMemoryLeaksOnShutdown := True détecte les fuites à la sortie du programme (builds de débogage). Pièges de performance courants en Delphi : (1) La concaténation de chaînes dans les boucles crée des copies — utilisez TStringBuilder ou pré-allouez. (2) SetLength dans une boucle réalloue — définissez la taille une fois. (3) Passer des chaînes/tableaux par valeur les copie — utilisez 'const' pour les paramètres en lecture seule. (4) TStringList.Sorted + Find est O(log n) ; IndexOf non trié est O(n). (5) TList<T>.Add est O(1) amorti mais Insert au début est O(n). Pour le profilage approfondi, utilisez Sampling Profiler (gratuit), AQTime ou GpProfile — ils identifient les points chauds sans modifications de code. Optimisez les 20% de code qui prennent 80% du temps.

delphi
uses System.Diagnostics;

// TStopwatch - precise timing
var
  SW: TStopwatch;
  Elapsed: Int64;
begin
  SW := TStopwatch.StartNew;
  try
    ExpensiveOperation;
  finally
    SW.Stop;
    ShowMessage(Format('Elapsed: %d ms', [SW.ElapsedMilliseconds]));
  end;
end;

// benchmark comparison
function Benchmark(const Name: string; const Action: TProc): Int64;
var
  SW: TStopwatch;
  I: Integer;
begin
  SW := TStopwatch.StartNew;
  for I := 1 to 1000 do
    Action;
  SW.Stop;
  WriteLn(Format('%s: %d ms', [Name, SW.ElapsedMilliseconds]));
  Result := SW.ElapsedMilliseconds;
end;

// memory usage
var
  Mem: TMemoryManagerState;
begin
  GetMemoryManagerState(Mem);
  ShowMessage(Format('Allocated: %d bytes', [Mem.TotalAllocated]));

  // report memory leaks on shutdown
  ReportMemoryLeaksOnShutdown := True;  // shows leak dialog on exit
end;

// common optimizations:
// 1. Use TStringBuilder for heavy string concatenation
var SB := TStringBuilder.Create;
try
  for var I := 1 to 10000 do
    SB.Append('Line ').Append(I).AppendLine;
  Result := SB.ToString;
finally
  SB.Free;
end;

// 2. SetLength once, not in a loop
SetLength(Result, Count);  // pre-allocate
for I := 0 to Count - 1 do
  Result[I] := Compute(I);

// 3. Use const for strings/arrays (avoids copy)
procedure Process(const Data: string);  // const = no copy

Gestion de la mémoire & Fuites

La gestion de la mémoire est la plus grande source de bugs en Delphi. Règle n°1 : chaque Create doit avoir un Free correspondant. Utilisez try/finally religieusement. Pour la gestion automatique, utilisez les interfaces (TInterfacedObject + comptage de références) — pas de Free nécessaire. TObjectList<T> avec OwnsObjects := True libère automatiquement les objets contenus. ReportMemoryLeaksOnShutdown := True affiche un dialogue listant les objets fuités à la sortie (débogage uniquement). FastMM (le gestionnaire de mémoire par défaut) en FullDebugMode journalise les fuites avec les traces de pile d'allocation vers un fichier — essentiel pour traquer les fuites. Modèles de fuite courants : try/finally manquant, gestionnaires d'événements non supprimés, références circulaires (corrigez avec [Weak]), threads non libérés, objets globaux non libérés dans finalization. La section finalization de l'unité s'exécute à l'arrêt — utilisez-la pour le nettoyage global.

delphi
// Rule: every Create needs a Free (or use interfaces)

// Pattern 1: try/finally
var
  Obj: TMyObject;
begin
  Obj := TMyObject.Create;
  try
    Obj.DoWork;
  finally
    Obj.Free;    // always freed
  end;
end;

// Pattern 2: interface reference counting (automatic)
var
  Obj: IMyInterface;
begin
  Obj := TMyObject.Create;  // TInterfacedObject
  Obj.DoWork;
  // freed automatically when Obj goes out of scope
end;

// Pattern 3: TObjectList (owns children)
var
  List: TObjectList<TPerson>;
begin
  List := TObjectList<TPerson>.Create(True);  // OwnsObjects
  try
    List.Add(TPerson.Create('Alice'));
    List.Add(TPerson.Create('Bob'));
    // freeing List frees all TPerson objects
  finally
    List.Free;
  end;
end;

// Detecting leaks
// 1. ReportMemoryLeaksOnShutdown := True;
// 2. FastMM (default since D2006) with FullDebugMode
//    → logs leaks with stack traces to file
// 3. Set breakpoint on System._DebugIntfMemLeak (FastMM)

// Common leak causes:
// - Create without Free (missing try/finally)
// - Event handler assigned but never removed
// - Circular references (use [Weak])
// - TThread not freed (FreeOnTerminate := True)
// - Global objects not freed in finalization

// finalization section for globals
var
  GlobalCache: TDictionary<string, TObject>;
initialization
  GlobalCache := TDictionary<string, TObject>.Create;
finalization
  GlobalCache.Free;    // cleanup on shutdown

Qualité du code & Tests

DUnitX est le framework de tests unitaires moderne (remplace DUnit). [TestFixture] marque les classes de test, [Test] marque les méthodes de test, [Setup]/[TearDown] s'exécutent avant/après chaque test. [TestCase] paramètre les tests avec des données en ligne. Assert.AreEqual/IsTrue/WillRaise vérifient les résultats. Développement piloté par les tests (TDD) : écrivez les tests en premier, puis le code. Les tests détectent les régressions et documentent le comportement attendu. Delphi Mocks (ou Spring4D mocking) crée des objets mock à partir d'interfaces — Setup.Expect définit les attentes, VerifyAll vérifie qu'elles ont été respectées. Le mocking est essentiel pour isoler les unités (mock base de données, réseau, système de fichiers). Visez une couverture élevée de la logique métier. Exécutez les tests en CI (intégration continue) pour détecter les régressions tôt. Les tests d'intégration vérifient que les composants fonctionnent ensemble ; les tests unitaires vérifient les unités individuelles de manière isolée.

delphi
// DUnitX - unit testing framework
uses DUnitX.TestFramework;

type
  [TestFixture]
  TCalculatorTests = class
  public
    [Setup]
    procedure Setup;

    [TearDown]
    procedure TearDown;

    [Test]
    procedure TestAdd;

    [Test]
    [TestCase('A', '1,2,3')]
    [TestCase('B', '10,20,30')]
    procedure TestAddParam(A, B, Expected: Integer);

    [Test]
    procedure TestDivideByZero;
  end;

procedure TCalculatorTests.TestAdd;
var
  Calc: TCalculator;
begin
  Calc := TCalculator.Create;
  try
    Assert.AreEqual(5, Calc.Add(2, 3));
    Assert.AreNotEqual(6, Calc.Add(2, 3));
    Assert.IsTrue(Calc.Add(0, 0) = 0);
  finally
    Calc.Free;
  end;
end;

procedure TCalculatorTests.TestDivideByZero;
var
  Calc: TCalculator;
begin
  Calc := TCalculator.Create;
  try
    Assert.WillRaise(
      procedure
      begin
        Calc.Divide(10, 0);
      end, EDivByZero);
  finally
    Calc.Free;
  end;
end;

// mock with interfaces
type
  [Mock]
  ILogger = interface
    ['{...}']
    procedure Log(const Msg: string);
  end;

// Delphi Mocks framework
var
  MockLogger: TMock<ILogger>;
begin
  MockLogger := TMock<ILogger>.Create;
  MockLogger.Setup.Expect.Once.When.Log('test');
  // ... use MockLogger.Object ...
  MockLogger.VerifyAll;  // asserts Log was called
end;
19

Génériques & Collections

Déclaration de classe générique

Les génériques vous permettent d'écrire des conteneurs de type sûr sans transtypage. Déclarez avec <T> après le nom du type. Le compilateur génère une version spécialisée par type utilisé. Utilisez TArray<T> au lieu de array of pour les tableaux dynamiques dans les types génériques.

delphi
type
  TStack<T> = class
  private
    FItems: TArray<T>;
    FCount: Integer;
  public
    procedure Push(const AValue: T);
    function Pop: T;
    function Peek: T;
    function Count: Integer;
  end;

procedure TStack<T>.Push(const AValue: T);
begin
  if FCount = Length(FItems) then
    SetLength(FItems, FCount * 2 + 4);
  FItems[FCount] := AValue;
  Inc(FCount);
end;

Utilisation de TDictionary

TDictionary<K,V> est la table de hachage générique. Add lève une exception sur les clés en double ; OrAdd fait un upsert. TryGetValue retourne false (pas d'exception) sur une clé manquante. Libérez toujours les dictionnaires — ils ne possèdent aucun objet par défaut.

delphi
uses
  System.Generics.Collections;

var
  Dict: TDictionary<string, Integer>;
begin
  Dict := TDictionary<string, Integer>.Create;
  try
    Dict.Add('apple', 5);
    Dict.Add('banana', 3);
    Dict.OrAdd('apple', 10);  // add or replace

    if Dict.TryGetValue('apple', Value) then
      Writeln(Value);

    for var Pair in Dict do
      Writeln(Pair.Key, ': ', Pair.Value);
  finally
    Dict.Free;
  end;
end;

TList avec comparateur

TList<T>.Sort utilise IComparer<T>. TComparer<T>.Construct enveloppe une fonction anonyme dans un comparateur. BinarySearch nécessite que la liste soit triée avec le même comparateur. AddRange accepte un tableau ouvert ou une autre liste.

delphi
var
  List: TList<Integer>;
begin
  List := TList<Integer>.Create;
  try
    List.AddRange([5, 2, 8, 1, 9]);
    List.Sort;  // ascending

    // custom comparer (descending)
    List.Sort(TComparer<Integer>.Construct(
      function(const L, R: Integer): Integer
      begin
        Result := R - L;
      end));

    List.BinarySearch(8, Index);  // requires sorted list
  finally
    List.Free;
  end;
end;

Contraintes génériques

Les contraintes limitent les types substituables : 'class' (type référence), 'record' (type valeur), 'constructor' (constructeur sans paramètre) ou une classe ancêtre spécifique. Plusieurs contraintes séparées par des virgules. Sans 'constructor' vous ne pouvez pas appeler T.Create.

delphi
type
  TRepository<T: class, constructor> = class
  public
    function CreateInstance: T;
    procedure Save(const AEntity: T);
  end;

function TRepository<T>.CreateInstance: T;
begin
  Result := T.Create;  // requires 'constructor' constraint
end;

// multiple constraints: class, constructor, specific base
type
  TControlFactory<T: TControl, constructor> = class ... end;

Propriété d'objets avec TObjectDictionary

TObjectDictionary<K,V> étend TDictionary avec la propriété. Passez [doOwnsValues], [doOwnsKeys] ou les deux. Sur Remove/Clear/Free, les objets possédés sont libérés automatiquement — empêche les fuites de mémoire dans les collections d'objets.

delphi
var
  Dict: TObjectDictionary<string, TButton>;
begin
  // owns values — frees them automatically
  Dict := TObjectDictionary<string, TButton>.Create([doOwnsValues]);
  try
    Dict.Add('btn1', TButton.Create(nil));
    Dict.Add('btn2', TButton.Create(nil));
    Dict.Remove('btn1');  // frees the TButton
  finally
    Dict.Free;  // frees remaining buttons
  end;
end;
20

Méthodes anonymes & Fermetures

Méthode anonyme de base

Les méthodes anonymes sont des références de fonction en ligne. TFunc<...> est pour les fonctions, TProc<...> pour les procédures. Elles capturent les variables de la portée englobante (fermetures). Affectables à des variables, passables en paramètres.

delphi
var
  Adder: TFunc<Integer, Integer, Integer>;
begin
  Adder := function(A, B: Integer): Integer
    begin
      Result := A + B;
    end;

  Writeln(Adder(3, 4));  // 7
end;

Fermeture capturant des variables

Les variables capturées sont allouées sur le tas et vivent aussi longtemps que la méthode anonyme. Chaque appel à MakeMultiplier capture son propre Factor — les fermetures sont indépendantes. C'est ainsi que fonctionnent les fabriques et l'application partielle.

delphi
function MakeMultiplier(Factor: Integer): TFunc<Integer, Integer>;
begin
  Result := function(X: Integer): Integer
    begin
      Result := X * Factor;  // captures Factor
    end;
end;

var
  Double: TFunc<Integer, Integer>;
  Triple: TFunc<Integer, Integer>;
begin
  Double := MakeMultiplier(2);
  Triple := MakeMultiplier(3);
  Writeln(Double(10));  // 20
  Writeln(Triple(10));  // 30
end;

Fonctions d'ordre supérieur

'reference to' déclare un type procédural compatible avec les méthodes anonymes. Apply est une fonction d'ordre supérieur — prend une fonction en argument. Cela permet les modèles map/filter/reduce. Utilisez TArray<Integer> pour les tableaux dynamiques.

delphi
type
  TIntFunc = reference to function(X: Integer): Integer;

function Apply(const F: TIntFunc; Values: array of Integer): TArray<Integer>;
var
  I: Integer;
begin
  SetLength(Result, Length(Values));
  for I := 0 to High(Values) do
    Result[I] := F(Values[I]);
end;

var
  Squared: TArray<Integer>;
begin
  Squared := Apply(function(X: Integer): Integer
    begin
      Result := X * X;
    end, [1, 2, 3, 4, 5]);
end;

Gestionnaires d'événements avec fermetures

Les méthodes anonymes peuvent remplacer les gestionnaires d'événements traditionnels basés sur des méthodes, capturant le contexte sans champs. Utile pour les gestionnaires ponctuels et la réduction du code boilerplate. Le Caption capturé reste en vie avec la référence de fermeture détenue par OnClick.

delphi
procedure SetupButton(Button: TButton; const Caption: string);
begin
  Button.Caption := Caption;
  Button.OnClick := procedure(Sender: TObject)
    begin
      ShowMessage(Caption + ' clicked!');  // captures Caption
    end;
end;

// instead of:
//   procedure TForm1.Button1Click(Sender: TObject);
//   begin
//     ShowMessage('Button1 clicked!');
//   end;

TThread avec anonyme

CreateAnonymousThread enveloppe une fermeture dans un thread — travail en arrière-plan fire-and-forget. Utilisez TThread.Queue (ou Synchronize) pour marshaler les mises à jour d'interface vers le thread principal. Ne touchez jamais les contrôles d'interface directement depuis un thread de travail.

delphi
TThread.CreateAnonymousThread(
  procedure
  var
    I: Integer;
  begin
    for I := 1 to 10 do
    begin
      TThread.Queue(nil,
        procedure
        begin
          Memo1.Lines.Add('Progress: ' + I.ToString);
        end);
      Sleep(100);
    end;
  end).Start;
21

Attributs & RTTI

Déclaration d'attribut personnalisé

Les attributs sont des classes héritant de TCustomAttribute. Appliquez avec [AttrName(...)] sur les types, champs, méthodes, propriétés. Le compilateur les intègre dans le RTTI. Les paramètres du constructeur deviennent les arguments de l'attribut.

delphi
type
  DisplayNameAttribute = class(TCustomAttribute)
  private
    FName: string;
  public
    constructor Create(const AName: string);
    property Name: string read FName;
  end;

  constructor DisplayNameAttribute.Create(const AName: string);
  begin
    FName := AName;
  end;

  [DisplayName('User Account')]
  TUser = class
    [DisplayName('Full Name')]
    FName: string;
  end;

Lecture des attributs via RTTI

TRttiContext est le point d'entrée vers le RTTI. GetType retourne TRttiType pour une classe. GetAttributes retourne tous les attributs appliqués. Transtypez vers votre type d'attribut pour lire les propriétés. Le RTTI nécessite que la classe soit dans une unité compilée avec {$M+} ou dérivée de TPersistent.

delphi
uses
  System.Rtti;

var
  Ctx: TRttiContext;
  RttiType: TRttiType;
  Attr: TCustomAttribute;
begin
  Ctx := TRttiContext.Create;
  try
    RttiType := Ctx.GetType(TUser);
    for Attr in RttiType.GetAttributes do
      if Attr is DisplayNameAttribute then
        Writeln(DisplayNameAttribute(Attr).Name);
  finally
    Ctx.Free;
  end;
end;

RTTI des champs et méthodes

GetFields retourne tous les champs public/published. SetValue/GetValue fournissent un accès dynamique aux champs par nom — utile pour les sérialiseurs et ORM. GetMethods retourne toutes les méthodes y compris héritées. Le RTTI est plus lent que les appels directs.

delphi
var
  Ctx: TRttiContext;
  FieldType: TRttiField;
  Method: TRttiMethod;
  User: TUser;
begin
  Ctx := TRttiContext.Create;
  try
    User := TUser.Create;
    try
      for FieldType in Ctx.GetType(TUser).GetFields do
      begin
        Writeln(FieldType.Name, ': ', FieldType.FieldType.Name);
        FieldType.SetValue(User, 'Alice');  // set by RTTI
      end;
      Writeln(FieldType.GetValue(User).AsString);
    finally
      User.Free;
    end;
  finally
    Ctx.Free;
  end;
end;

RTTI des propriétés et invocation

GetProperties retourne les propriétés published. IsReadable/IsWritable vérifient les accesseurs. GetValue/SetValue fonctionnent aussi sur les propriétés. TypeKind (tkInteger, tkString, tkClass, etc.) vous permet de gérer chaque type de manière appropriée. C'est ainsi que fonctionnent la plupart des sérialiseurs Delphi.

delphi
var
  Ctx: TRttiContext;
  Prop: TRttiProperty;
  Instance: TMyClass;
begin
  Instance := TMyClass.Create;
  try
    for Prop in Ctx.GetType(TMyClass).GetProperties do
    begin
      if Prop.IsReadable then
        Writeln(Prop.Name, ' = ', Prop.GetValue(Instance).ToString);
      if Prop.IsWritable and (Prop.PropertyType.TypeKind = tkInteger) then
        Prop.SetValue(Instance, 42);
    end;
  finally
    Instance.Free;
  end;
end;

Invocation de méthode par nom

GetMethod trouve une méthode par nom (sensible à la casse). Invoke l'appelle dynamiquement avec des arguments de tableau TValue. TValue est un enveloppeur de type variant pour n'importe quel type. Utile pour les systèmes de plugins, le scripting et la liaison tardive. Retourne TValue — convertissez avec AsInteger, AsString, etc.

delphi
var
  Ctx: TRttiContext;
  Method: TRttiMethod;
  Args: array of TValue;
  Result: TValue;
begin
  Method := Ctx.GetType(TMyClass).GetMethod('CalculateTotal');
  if Assigned(Method) then
  begin
    SetLength(Args, 2);
    Args[0] := 10;
    Args[1] := 20;
    Result := Method.Invoke(MyInstance, Args);
    Writeln(Result.AsInteger);
  end;
end;
22

Interfaces approfondi

Déclaration et implémentation d'interface

Les interfaces définissent des contrats sans implémentation. Les GUIDs (optionnels mais recommandés) permettent les casts 'as' et Supports(). TInterfacedObject fournit le comptage de références. Toutes les méthodes d'interface doivent être implémentées (pas d'échappatoire 'abstract'). Les propriétés dans les interfaces nécessitent des méthodes accesseurs.

delphi
type
  IShape = interface
    ['{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}']
    function GetArea: Double;
    function GetPerimeter: Double;
    procedure Draw;
    property Color: TColor read FColor write SetColor;
  end;

  TCircle = class(TInterfacedObject, IShape)
  private
    FRadius: Double;
    FColor: TColor;
    procedure SetColor(Value: TColor);
  public
    constructor Create(ARadius: Double);
    function GetArea: Double;
    function GetPerimeter: Double;
    procedure Draw;
  end;

Comptage de références et mémoire

Les références d'interface sont à comptage de références. Lorsque la dernière référence d'interface sort de la portée, l'objet est libéré. NE MÉLANGEZ JAMAIS les références d'objet et d'interface vers la même instance — le comptage de références de l'interface la libérera alors que le pointeur d'objet pointe encore vers elle. Choisissez un modèle de propriété.

delphi
var
  Shape: IShape;
begin
  Shape := TCircle.Create(5.0);  // refcount = 1
  // ... use Shape ...
end;  // refcount drops to 0, object freed automatically

// Mixing interface and object references — DANGER:
var
  Obj: TCircle;
begin
  Obj := TCircle.Create(5.0);
  Shape := Obj;       // refcount = 1
  Shape := nil;       // refcount = 0, Obj freed!
  Obj.GetArea;        // AV — dangling pointer
end;

Héritage d'interface et interfaces multiples

Les interfaces peuvent hériter de plusieurs parents. Une classe peut implémenter plusieurs interfaces. Les clauses de résolution de méthode (method = interface.method) résolvent les conflits lorsque plusieurs interfaces déclarent la même méthode. Utilisez 'as' ou Supports() pour interroger une interface à l'exécution.

delphi
type
  IReadable = interface
    function Read: string;
  end;

  IWritable = interface
    procedure Write(const S: string);
  end;

  IStream = interface(IReadable, IWritable)
    procedure Flush;
  end;

  TFileStream = class(TInterfacedObject, IStream, IReadable, IWritable)
    // must implement all methods from all interfaces
  end;

Supports et casts as

Supports() vérifie si un objet implémente une interface — retourne un booléen, retourne optionnellement l'interface. Le cast 'as' fait de même mais lève EInvalidCast en cas d'échec. Supports() fonctionne à la fois sur les objets et les références d'interface. Nécessite que l'interface ait un GUID.

delphi
uses
  System.SysUtils, System.TypInfo;

var
  Obj: TObject;
  Shape: IShape;
begin
  Obj := TCircle.Create(5.0);
  try
    if Supports(Obj, IShape, Shape) then
      Writeln(Shape.GetArea:0:2);

    // 'as' cast — raises if not supported
    Shape := Obj as IShape;

    // type info
    if Supports(Obj, IShape) then
      Writeln('Obj supports IShape');
  finally
    Obj.Free;  // object reference — must free manually
  end;
end;

Modèle d'injection de dépendances

Passez les dépendances comme interfaces — permet le mocking, la permutation d'implémentations et la testabilité. La classe dépend de l'abstraction (ILogger), pas d'un type concret. C'est le fondement des conteneurs DI comme Spring4D. La propriété d'interface signifie que le journaliseur vit aussi longtemps que le service détient la référence.

delphi
type
  ILogger = interface
    procedure Log(const Msg: string);
  end;

  TOrderService = class
  private
    FLogger: ILogger;
  public
    constructor Create(ALogger: ILogger);
    procedure ProcessOrder(OrderId: Integer);
  end;

constructor TOrderService.Create(ALogger: ILogger);
begin
  FLogger := ALogger;  // injected dependency
end;

procedure TOrderService.ProcessOrder(OrderId: Integer);
begin
  FLogger.Log('Processing order ' + OrderId.ToString);
end;
23

Gestion de la mémoire avancée

Modèle try-finally

Associez toujours l'allocation avec Free dans try-finally. Imbriquez les blocs finally pour plusieurs ressources. FreeAndNil (au lieu de Free) efface aussi la variable — utile pour détecter l'utilisation après libération. Free est sûr sur nil — pas besoin de vérifier Assigned d'abord.

delphi
var
  List: TObjectList;
  Stream: TFileStream;
begin
  List := TObjectList.Create;
  try
    Stream := TFileStream.Create('data.bin', fmOpenRead);
    try
      // ... use Stream ...
    finally
      Stream.Free;
    end;
  finally
    List.Free;
  end;
end;

Propriété basée sur les interfaces

TInterfacedObject + référence d'interface = nettoyage automatique. Lorsque l'interface sort de la portée, le destructeur s'exécute. C'est RAII en Delphi — enveloppez les ressources dans des objets d'interface pour un nettoyage garanti sans boilerplate try-finally.

delphi
type
  TTempFile = class(TInterfacedObject)
  private
    FName: string;
  public
    constructor Create(const AName: string);
    destructor Destroy; override;
  end;

destructor TTempFile.Destroy;
begin
  if FileExists(FName) then
    DeleteFile(FName);
  inherited;
end;

// usage:
var
  Temp: TTempFile;
begin
  Temp := TTempFile.Create('tmp.txt');
  // ... use file ...
end;  // Temp freed automatically (refcount)

Références faibles

Les références faibles cassent les cycles de référence. Sans [Weak], deux objets se tenant des références d'interface l'un à l'autre ne seraient jamais libérés (cycle). TComponent a un mécanisme FreeNotification intégré pour les références faibles. L'attribut [Weak] nécessite le RTTI et fonctionne sur les champs d'interface et de classe.

delphi
type
  [Weak]
  FParent: TComponent;  // weak reference — no refcount increment

  // or for interfaces:
  [Weak]
  FLogger: ILogger;

// TComponent uses notification-based weak refs:
type
  TChild = class(TComponent)
  private
    FParent: TComponent;
  public
    property Parent: TComponent read FParent write FParent;
  end;

Enregistrements vs objets

Les enregistrements sont des types valeur (pile, copiés à l'affectation) — pas de gestion de mémoire nécessaire. Les classes sont des types référence (tas, doivent être libérées). Utilisez les enregistrements pour les petites données immuables (points, dates, argent). Utilisez les classes pour les objets polymorphes ou grands. Les enregistrements peuvent avoir des méthodes et des opérateurs en Delphi moderne.

delphi
type
  TPoint = record  // value type — stack allocated
    X, Y: Double;
    function Distance: Double;
  end;

  TPointObj = class  // reference type — heap allocated
    X, Y: Double;
    function Distance: Double;
  end;

var
  P1, P2: TPoint;
  O1, O2: TPointObj;
begin
  P1 := P2;  // copies values
  O1 := O2;  // copies reference (both point to same object)
end;

Détection des fuites de mémoire

ReportMemoryLeaksOnShutdown affiche un dialogue à la sortie listant les objets fuités. FastMM (le gestionnaire de mémoire par défaut) détecte les fuites, les double-libérations et l'utilisation après libération. Pour la production, journalisez les fuites vers un fichier. Exécutez des vérifications de fuites régulièrement pendant le développement — plus facile de corriger les fuites au fur et à mesure qu'elles sont introduites.

delphi
uses
  System.ReportMemoryLeaksOnShutdown;

begin
  ReportMemoryLeaksOnShutdown := True;
  // ... your code ...
  // on app exit, leak report shown if any unfreed objects
end;

// FastMM4 (built into modern Delphi):
//   - Detects leaks with call stack
//   - Reports type and count of leaked objects
//   - Use FullDebugMode for detailed diagnostics

// Manual check:
var
  StartMem: Integer;
begin
  StartMem := AllocMemSize;
  // ... code under test ...
  if AllocMemSize > StartMem then
    Writeln('Memory leak detected');
end;
24

FireMonkey (FMX)

Bases des formulaires multiplateformes

Les formulaires FMX sont multiplateformes (Windows, macOS, iOS, Android, Linux). Même code, différents rendus natifs. Utilisez les unités FMX.* au lieu de Vcl.* Les contrôles sont basés sur des vecteurs (mise à l'échelle parfaite). Les styles remplacent les thèmes — l'apparence visuelle est pilotée par les données.

delphi
unit MainForm;

interface

uses
  System.SysUtils, System.Types, FMX.Forms, FMX.Controls,
  FMX.Controls.Presentation, FMX.Edit, FMX.Buttons;

type
  TFormMain = class(TForm)
    EditName: TEdit;
    ButtonSubmit: TSpeedButton;
    procedure ButtonSubmitClick(Sender: TObject);
  private
    FName: string;
  public
    property Name: string read FName;
  end;

var
  FormMain: TFormMain;

implementation

procedure TFormMain.ButtonSubmitClick(Sender: TObject);
begin
  FName := EditName.Text;
  Close;
end;

end.

Dispositions et alignement

FMX utilise Align (Client, Top, Bottom, Left, Right, None) et Margins/Padding pour la disposition. TFlowLayout arrange les enfants comme CSS flexbox. TGridLayout fait une grille. Utilisez TScaleBox pour une mise à l'échelle indépendante de la résolution. Les dispositions sont elles-mêmes des contrôles — imbriquables.

delphi
// Layout types: TLayout, TFlowLayout, TGridLayout, TScrollBox
var
  Layout: TFlowLayout;
  Btn: TButton;
begin
  Layout := TFlowLayout.Create(Self);
  Layout.Parent := Self;
  Layout.Align := TAlignLayout.Client;
  Layout.FlowDirection := TFlowDirection.LeftToRight;
  Layout.Justify := TJustifyMode.SpaceBetween;

  for var I := 1 to 5 do
  begin
    Btn := TButton.Create(Self);
    Btn.Parent := Layout;
    Btn.Text := 'Button ' + I.ToString;
    Btn.Margins.Rect := RectF(5, 5, 5, 5);
  end;
end;

Styles et stylisation

Les styles sont des collections de ressources visuelles (brosses, polices, effets) stockées dans des fichiers .fsf ou .style. StyleLookup choisit un style nommé pour un contrôle. TStyleManager permute les styles globaux à l'exécution. Les styles FMX sont vectoriels — mise à l'échelle à n'importe quel DPI. Le Style Designer édite les styles visuellement.

delphi
// Load a custom style
begin
  TStyleManager.LoadFromFile('Dark.fsf');
  TStyleManager.TrySetStyleFromResource('DarkStyle');
end;

// Apply style to a single control:
Button1.StyleLookup := 'cornerbutton';

// Read style in code:
var
  StyleObj: TFmxObject;
begin
  StyleObj := TStyleManager.ActiveStyle(Self).FindStyleResource('buttonstyle');
end;

// LiveBindings designer for visual data binding
// Tools > LiveBindings Designer

Effets et animations

Les effets (Glow, Shadow, Blur, Reflection) sont des composants non visuels parentés à un contrôle. Les animations (TFloatAnimation, TColorAnimation, TPathAnimation) animent des propriétés dans le temps. Définissez Parent vers le contrôle cible. Trigger/Start pour commencer. Tout accéléré par GPU — fluide sur toutes les plateformes.

delphi
uses
  FMX.Effects, FMX.Ani;

var
  Glow: TGlowEffect;
  Ani: TFloatAnimation;
begin
  // Glow effect on a button
  Glow := TGlowEffect.Create(Button1);
  Glow.Parent := Button1;
  Glow.GlowColor := TAlphaColors.Blue;
  Glow.Enabled := True;

  // Animate opacity
  Ani := TFloatAnimation.Create(Button1);
  Ani.Parent := Button1;
  Ani.PropertyName := 'Opacity';
  Ani.StartValue := 0;
  Ani.EndValue := 1;
  Ani.Duration := 0.5;
  Ani.Start;
end;

Services de plateforme

Les services de plateforme abstraient les fonctionnalités spécifiques à l'OS. Interrogez avec SupportsPlatformService — retourne false sur les plateformes non prises en charge. Vérifiez toujours avant utilisation. Services courants : presse-papiers, dialogues, clavier virtuel, infos appareil, écran. Ce modèle garde votre code multiplateforme sans blocs {$IFDEF}.

delphi
uses
  FMX.Platform;

var
  ScreenSvc: IFMXScreenService;
  Size: TPoint;
begin
  if TPlatformServices.Current.SupportsPlatformService(
    IFMXScreenService, IInterface(ScreenSvc)) then
  begin
    Size := ScreenSvc.GetScreenSize;
    Writeln(Size.X.ToString, 'x', Size.Y.ToString);
  end;
end;

// Other services:
//   IFMXClipboardService
//   IFMXDialogService (async message boxes)
//   IFMXVirtualKeyboardService
//   IFMXDeviceService
25

Base de données (FireDAC)

Configuration de connexion

TFDConnection est l'objet FireDAC central. Définissez DriverName (SQLite, MSSQL, MySQL, PostgreSQL, Oracle, etc.) et Params. Les définitions de connexion peuvent être stockées dans un fichier .ini pour réutilisation. Définissez toujours Connected := False avant de libérer. Utilisez TFDManager pour le pool de connexions.

delphi
uses
  FireDAC.Comp.Client, FireDAC.Stan.Def;

var
  FDConn: TFDConnection;
begin
  FDConn := TFDConnection.Create(nil);
  try
    FDConn.DriverName := 'SQLite';
    FDConn.Params.Database := 'app.db';
    FDConn.Params.Add('Encrypt=AES-256');
    FDConn.Params.Password := 'secret';
    FDConn.Connected := True;

    // or use a connection definition file:
    // FDConn.ConnectionDefName := 'MySQLite';
  finally
    FDConn.Free;
  end;
end;

Exécution de requête

Utilisez Open pour SELECT (retourne un curseur), ExecSQL pour INSERT/UPDATE/DELETE (retourne les lignes affectées). Utilisez TOUJOURS des paramètres — ne concaténez jamais de valeurs dans SQL (risque d'injection). ParamByName est insensible à la casse. FieldByName accède aux colonnes par nom. Eof/Next itèrent les lignes.

delphi
var
  Query: TFDQuery;
begin
  Query := TFDQuery.Create(nil);
  try
    Query.Connection := FDConn;

    // parameterized query (prevents SQL injection)
    Query.SQL.Text := 'SELECT * FROM users WHERE age > :min_age';
    Query.ParamByName('min_age').AsInteger := 18;
    Query.Open;

    while not Query.Eof do
    begin
      Writeln(Query.FieldByName('name').AsString);
      Query.Next;
    end;

    // execute non-query (INSERT/UPDATE/DELETE)
    Query.SQL.Text := 'INSERT INTO users (name, age) VALUES (:n, :a)';
    Query.ParamByName('n').AsString := 'Alice';
    Query.ParamByName('a').AsInteger := 30;
    Query.ExecSQL;
  finally
    Query.Free;
  end;
end;

Transactions

StartTransaction/Commit/Rollback enveloppent les opérations atomiques. Si une instruction échoue, Rollback annule toutes les modifications. Les transactions imbriquées utilisent des savepoints (annulation partielle). Enveloppez toujours dans try-except-raise pour propager l'erreur après rollback. Sans transaction, chaque instruction auto-valide.

delphi
FDConn.StartTransaction;
try
  Query.SQL.Text := 'UPDATE accounts SET balance = balance - 100 WHERE id = 1';
  Query.ExecSQL;
  Query.SQL.Text := 'UPDATE accounts SET balance = balance + 100 WHERE id = 2';
  Query.ExecSQL;

  FDConn.Commit;
except
  FDConn.Rollback;
  raise;
end;

// nested transactions via savepoints:
FDConn.StartTransaction;
try
  // ... work ...
  FDConn.StartTransaction;  // savepoint
  try
    // ... more work ...
    FDConn.Commit;
  except
    FDConn.Rollback;  // rolls back to savepoint
  end;
finally
  FDConn.Commit;
end;

TFDTable et données en direct

TFDTable est un curseur en direct et modifiable sur une table. Edit/Post modifie la ligne courante. Append/Post insère. Delete supprime la ligne courante. Les modifications vont directement à la base de données. Utilisez IndexFieldNames pour le tri. Pour les requêtes complexes, utilisez TFDQuery à la place.

delphi
var
  Table: TFDTable;
begin
  Table := TFDTable.Create(nil);
  try
    Table.Connection := FDConn;
    Table.TableName := 'users';
    Table.IndexFieldNames := 'name';  // ORDER BY
    Table.Open;  // SELECT * FROM users

    // edit current row
    Table.Edit;
    Table.FieldByName('age').AsInteger := 31;
    Table.Post;

    // insert new row
    Table.Append;
    Table.FieldByName('name').AsString := 'Bob';
    Table.FieldByName('age').AsInteger := 25;
    Table.Post;

    // delete current row
    Table.Delete;
  finally
    Table.Free;
  end;
end;

Mises à jour par lot et mode cache

Le mode CachedUpdates met en mémoire tampon les modifications — appliquez-les toutes en une fois avec ApplyUpdates. Plus rapide que les mises à jour par ligne pour les opérations en masse. CancelUpdates rejette le tampon. Status montre le type de modification par ligne. Utile pour les scénarios déconnectés et la réduction des allers-retours.

delphi
Query.CachedUpdates := True;
Query.Open;

// make many changes locally
while not Query.Eof do
begin
  Query.Edit;
  Query.FieldByName('status').AsString := 'processed';
  Query.Post;
  Query.Next;
end;

// apply all changes in one transaction
FDConn.StartTransaction;
try
  Query.ApplyUpdates;
  FDConn.Commit;
except
  FDConn.Rollback;
  Query.CancelUpdates;
  raise;
end;

// inspect change log:
Query.Status;  // TUpdateStatus (usModified, usInserted, usDeleted)
26

REST & HTTP

Bases de TRESTClient

TRESTClient détient l'URL de base. TRESTRequest construit la requête (méthode, ressource, paramètres). TRESTResponse détient le résultat. Les segments d'URL ({id}) sont substitués par AddUrlSegment. StatusCode/Content donnent la réponse HTTP. Libérez dans l'ordre inverse de création.

delphi
uses
  REST.Client, REST.Types;

var
  Client: TRESTClient;
  Request: TRESTRequest;
  Response: TRESTResponse;
begin
  Client := TRESTClient.Create('https://api.example.com');
  Request := TRESTRequest.Create(Client);
  Response := TRESTResponse.Create(Client);
  try
    Request.Resource := '/users/{id}';
    Request.Method := TRESTRequestMethod.rmGET;
    Request.Params.AddUrlSegment('id', '42');
    Request.Params.AddItem('fields', 'name,email', TRESTRequestParameterKind.pkGETorPOST);

    Request.Execute;

    if Response.StatusCode = 200 then
      Writeln(Response.Content);
  finally
    Response.Free;
    Request.Free;
    Client.Free;
  end;
end;

Analyse JSON

System.JSON fournit TJSONObject, TJSONArray, TJSONValue. ParseJSONValue analyse une chaîne (retourne TJSONValue — transtypez au besoin). GetValue<T> lit les valeurs typées. AddPair/AddElement construisent le JSON. Tous les objets JSON doivent être libérés — ils ne sont à comptage de références que lorsqu'ils sont possédés par un parent.

delphi
uses
  System.JSON;

var
  JSON: TJSONObject;
  Arr: TJSONArray;
  Item: TJSONObject;
  I: Integer;
begin
  // parse
  JSON := TJSONObject.ParseJSONValue('{"name":"Alice","age":30}') as TJSONObject;
  try
    Writeln(JSON.GetValue<string>('name'));
    Writeln(JSON.GetValue<Integer>('age'));
  finally
    JSON.Free;
  end;

  // build
  JSON := TJSONObject.Create;
  try
    JSON.AddPair('name', 'Bob');
    JSON.AddPair('scores', TJSONArray.Create(90, 85, 92));
  finally
    JSON.Free;
  end;
end;

Serveur REST avec datasnap

DataSnap expose les méthodes Delphi comme des points de terminaison REST automatiquement. Les noms de méthode deviennent des segments d'URL. Les paramètres mappent vers les segments d'URL ou le corps POST. TJSONObject/TJSONArray sont les types de retour standard. Appliquez des attributs comme [httppost] pour spécifier les verbes HTTP. Utilisez TDSServerModule comme classe de base.

delphi
// ServerContainerUnit1.pas
type
  TServerMethods1 = class(TDSServerModule)
    function GetUsers: TJSONArray;
    function GetUser(id: Integer): TJSONObject;
    [httppost] function CreateUser(Data: TJSONObject): TJSONObject;
  end;

function TServerMethods1.GetUsers: TJSONArray;
begin
  Result := TJSONArray.Create;
  // ... populate from DB ...
end;

// Access via URL:
//   GET  http://localhost:8080/datasnap/rest/TServerMethods1/GetUsers
//   GET  http://localhost:8080/datasnap/rest/TServerMethods1/GetUser/42
//   POST http://localhost:8080/datasnap/rest/TServerMethods1/CreateUser

Indy HTTP pour contrôle de bas niveau

TIdHTTP (Indy) donne un contrôle complet sur HTTP — en-têtes, cookies, redirections, timeouts. Plus verbeux que TRESTClient mais plus flexible. Pour HTTPS, affectez un IOHandler SSL (TIdSSLIOHandlerSocketOpenSSL). Définissez ReadTimeout/ConnectTimeout pour la production. Indy est synchrone — enveloppez dans TThread pour async.

delphi
uses
  IdHTTP, IdGlobal;

var
  Http: TIdHTTP;
  Response: string;
  PostData: TStringStream;
begin
  Http := TIdHTTP.Create(nil);
  try
    Http.Request.ContentType := 'application/json';
    Http.Request.CustomHeaders.AddValue('Authorization', 'Bearer token123');

    // GET
    Response := Http.Get('https://api.example.com/users');

    // POST
    PostData := TStringStream.Create('{"name":"Alice"}', TEncoding.UTF8);
    try
      Response := Http.Post('https://api.example.com/users', PostData);
    finally
      PostData.Free;
    end;

    Writeln(Http.ResponseCode);  // 200, 404, etc.
  finally
    Http.Free;
  end;
end;

HTTP asynchrone avec tâches

Enveloppez les appels REST dans TTask.Run pour éviter de bloquer le thread d'interface. Marshaler les mises à jour d'interface avec TThread.Queue (async) ou TThread.Synchronize (sync). Attention aux durées de vie des objets — la requête doit survivre à la tâche. Envisagez TRESTRequest.ExecuteAsync pour le support async intégré.

delphi
uses
  System.Threading, REST.Client;

var
  Request: TRESTRequest;
begin
  Request := TRESTRequest.Create(nil);
  try
    Request.Client := TRESTClient.Create('https://api.example.com/users');
    Request.Client.Owner := Request;

    TTask.Run(
      procedure
      begin
        Request.Execute;  // background thread

        TThread.Queue(nil,
          procedure
          begin
            // update UI on main thread
            Memo1.Lines.Text := Request.Response.Content;
          end);
      end);
  finally
    // don't free Request here — task may still be running
  end;
end;
27

Multithreading (Parallèle)

Bases de TThread

Sous-classez TThread et surchargez Execute. Create(False) démarre immédiatement ; Create(True) nécessite .Start. FreeOnTerminate := True libère automatiquement — n'appelez jamais Free sur de tels threads. Vérifiez Terminated périodiquement pour un arrêt gracieux. Ne touchez jamais l'interface depuis Execute — utilisez Synchronize/Queue.

delphi
type
  TWorker = class(TThread)
  protected
    procedure Execute; override;
  public
    constructor Create;
  end;

procedure TWorker.Execute;
var
  I: Integer;
begin
  for I := 1 to 100 do
  begin
    if Terminated then Exit;
    // ... work ...
    Sleep(50);
  end;
end;

constructor TWorker.Create;
begin
  inherited Create(False);  // False = start immediately
  FreeOnTerminate := True;  // auto-free on completion
end;

TTask et futures

ITask/IFuture<T> de System.Threading sont de plus haut niveau que TThread. Les futures retournent une valeur typée — .Value bloque jusqu'à ce que le résultat soit prêt. Les tâches sont à comptage de références (pas de Free manuel). Utilisez TTask.WaitForAll / WaitForAny pour coordonner plusieurs tâches. Plus faciles à utiliser que TThread brut.

delphi
uses
  System.Threading;

var
  Task: ITask;
  Future: IFuture<string>;
begin
  // fire-and-forget task
  Task := TTask.Create(
    procedure
    begin
      // ... background work ...
    end);
  Task.Start;

  // future — returns a value
  Future := TTask.Future<string>(
    function: string
    begin
      Sleep(1000);
      Result := 'computed value';
    end);

  // ... do other work ...

  Writeln(Future.Value);  // blocks until ready
end;

Boucle parallel for

TParallel.For exécute les itérations de boucle en parallèle sur les cœurs CPU. DOIT synchroniser l'état partagé (utilisez TCriticalSection ou TInterlocked). L'ordre d'itération est non déterministe. Utilisez TLoopState pour break/continue. Plus rapide pour le travail lié au CPU ; plus lent pour les itérations triviales en raison du surcoût.

delphi
uses
  System.Threading, System.SyncObjs;

var
  Total: Integer;
  Lock: TCriticalSection;
  I: Integer;
begin
  Lock := TCriticalSection.Create;
  try
    TParallel.For(1, 1000,
      procedure(I: Integer)
      begin
        Lock.Enter;
        try
          Total := Total + ComputeExpensive(I);
        finally
          Lock.Leave;
        end;
      end);
  finally
    Lock.Free;
  end;
end;

Primitives de synchronisation

TCriticalSection : exclusion mutuelle (un seul thread à la fois). TEvent : signaler entre threads (SetEvent/WaitFor). TEvent avec réinitialisation manuelle reste signalé jusqu'à Reset. TInterlocked.Increment est atomique et plus rapide qu'une section critique pour les compteurs simples. TMonitor (intégré à TObject) est une autre option.

delphi
uses
  System.SyncObjs;

var
  Lock: TCriticalSection;
  Event: TEvent;
  Count: Integer;
begin
  Lock := TCriticalSection.Create;
  Event := TEvent.Create(nil, True, False, '');
  try
    TThread.CreateAnonymousThread(
      procedure
      begin
        Lock.Enter;
        try
          Inc(Count);
        finally
          Lock.Leave;
        end;
        Event.SetEvent;  // signal completion
      end).Start;

    Event.WaitFor(INFINITE);  // wait for signal
  finally
    Lock.Free;
    Event.Free;
  end;
end;

TThread.Queue et Synchronize

Les contrôles d'interface ne peuvent être touchés que depuis le thread principal. Synchronize bloque le worker jusqu'à ce que le thread principal exécute la méthode anonyme — utilisez avec parcimonie (cause la sérialisation). Queue publie et continue — préféré pour les mises à jour d'interface fire-and-forget. Passez nil comme argument de thread pour utiliser le thread courant.

delphi
// From a worker thread, update UI safely:

// Synchronous — blocks worker until main thread runs the code
TThread.Synchronize(nil,
  procedure
  begin
    Label1.Caption := 'Done';
  end);

// Asynchronous — posts to main thread queue, doesn't block
TThread.Queue(nil,
  procedure
  begin
    Label1.Caption := 'Progress: 50%';
  end);

// TThread.Queue is preferred for non-critical updates
// Synchronize for cases where you need the result before continuing
28

Packages & Composants

Bases du projet de package

Les packages (.bpl) sont des DLLs avec métadonnées Delphi — partagent du code entre applications. 'requires' liste les dépendances. 'contains' liste les unités dans ce package. Les packages de conception installent des composants dans l'IDE ; les packages d'exécution sont livrés avec l'application. Séparez conception/exécution pour réduire l'encombrement de l'IDE.

delphi
// MyPackage.dpk
package MyPackage;

{$R *.res}
{$ALIGN 8}
{$ASSERTIONS ON}
{$DESIGNONLY MyDesignUnits}  // design-time only
{$RUNONLY MyRuntimeUnits}    // runtime only

requires
  rtl,
  vcl,
  System.Generics.Collections;

contains
  MyUnit1 in 'MyUnit1.pas',
  MyUnit2 in 'MyUnit2.pas',
  MyComponent in 'MyComponent.pas';

end.

// Build configurations:
//   - Build (debug)
//   - Release
//   - Design-time (installs into IDE)
//   - Runtime (deployed with app)

Squelette de composant personnalisé

Dérivez de la classe existante la plus proche (TCustomLabel donne un label sans propriétés published). Republiez uniquement les propriétés que vous voulez exposer. La procédure Register ajoute le composant à la palette de l'IDE. 'default' définit la valeur initiale (doit correspondre au constructeur). Placez Register dans un package de conception.

delphi
unit MyLabel;

interface

uses
  Vcl.Controls, Vcl.Graphics, Vcl.StdCtrls;

type
  TMyLabel = class(TCustomLabel)
  private
    FHighlightColor: TColor;
    procedure SetHighlightColor(Value: TColor);
  protected
    procedure Paint; override;
  public
    constructor Create(AOwner: TComponent); override;
  published
    property HighlightColor: TColor
      read FHighlightColor write SetHighlightColor default clYellow;
    property Caption;
    property Font;
    property OnClick;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('MyPalette', [TMyLabel]);
end;

Propriétés de composant et éditeurs

TComponent est la base pour les composants non visuels. Possédez les sous-objets (FItems) — créez dans le constructeur, libérez dans le destructeur. Les propriétés TStrings obtiennent un éditeur de chaîne intégré. RegisterPropertyEditor personnalise l'Inspecteur d'objets pour des propriétés spécifiques. Utilisez TPersistent pour les objets imbriqués nécessitant du streaming.

delphi
type
  TMyComponent = class(TComponent)
  private
    FItems: TStringList;
    function GetItems: TStrings;
    procedure SetItems(Value: TStrings);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Items: TStrings read GetItems write SetItems;
  end;

constructor TMyComponent.Create(AOwner: TComponent);
begin
  inherited;
  FItems := TStringList.Create;
end;

destructor TMyComponent.Destroy;
begin
  FItems.Free;
  inherited;
end;

// Property editor for rich editing in Object Inspector:
//   RegisterPropertyEditor(TypeInfo(TStrings), TMyComponent, 'Items',
//     TStringListProperty);

Événements et pointeurs de méthode

Les types d'événement sont des types procéduraux avec 'of object' — ils détiennent à la fois une référence d'objet et un pointeur de méthode. Vérifiez toujours Assigned() avant d'appeler — les événements nil lèvent des AV. Les méthodes Do* (DoChange, DoClick) sont les répartiteurs protégés qui déclenchent les événements. Les sous-classes peuvent surcharger Do* pour intercepter les événements.

delphi
type
  TMyEvent = procedure(Sender: TObject; Value: Integer) of object;

  TMyComponent = class(TComponent)
  private
    FOnChange: TMyEvent;
  protected
    procedure DoChange(Value: Integer);
  published
    property OnChange: TMyEvent read FOnChange write FOnChange;
  end;

procedure TMyComponent.DoChange(Value: Integer);
begin
  if Assigned(FOnChange) then
    FOnChange(Self, Value);
end;

// 'of object' makes it a method pointer — must be assigned to a method
// (e.g., Form1.Button1Click). Always check Assigned before calling.

Streaming et persistance

TPersistent permet le streaming et Assign. Les propriétés published sont automatiquement sauvegardées dans les fichiers DFM. Surchargez Assign pour prendre en charge la copie entre objets. DefineProperties ajoute des données non published au flux. WriteComponent/ReadComponent sérialisent vers n'importe quel TStream. C'est ainsi que les formulaires persistent leur état.

delphi
type
  TMySettings = class(TPersistent)
  private
    FTimeout: Integer;
    FTitle: string;
  published
    property Timeout: Integer read FTimeout write FTimeout default 30;
    property Title: string read FTitle write FTitle;
  end;

// TPersistent enables streaming (DFM, RTTI):
//   - Inherits from TPersistent (gives Assign)
//   - Published properties are streamed
//   - Override AssignTo/Assign for custom copy

// Save to DFM automatically:
//   - Component owned by a form is streamed
//   - Sub-properties (TPersistent) are nested in DFM
//   - Use DefineProperties for non-standard data

// Manual streaming:
var
  Stream: TFileStream;
begin
  Stream := TFileStream.Create('settings.bin', fmCreate);
  try
    Stream.WriteComponent(MyComponent);
  finally
    Stream.Free;
  end;
end;

Was this helpful?