Skip to content

Delphi Hoja de referencia

Dialecto de Object Pascal para desarrollo rápido de aplicaciones.

01

Estructura del Programa y Fundamentos

Estructura del Programa y Units

Un programa Delphi comienza con 'program' y termina con 'end.' (punto). {$APPTYPE CONSOLE} es una directiva del compilador que lo marca como aplicación de consola. 'uses' importa units (módulos) — System.SysUtils tiene Format, IntToStr, etc. Las units tienen una sección interface (declaraciones públicas) y una sección implementation (código). WriteLn genera texto con una nueva línea; Write genera sin ella. ReadLn lee entrada (o pausa). El bloque begin..end principal es el punto de entrada del programa.

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, Tipos y Constantes

Delphi es fuertemente tipado. Tipos comunes: Integer (32-bit), Int64 (64-bit), Double (64-bit float), Extended (80-bit float en x86), Single (32-bit float), string (Unicode, con conteo de referencias), Char (WideChar, 2 bytes), Boolean, Byte (0-255). TDateTime es en realidad un Double (días desde 1899-12-30). Las constantes usan 'const' — las constantes tipadas tienen un tipo, las no tipadas son flexibles. Los tipos subrange (0..150) restringen valores. Las enumeraciones (TDay) definen constantes nombradas. Format() es como sprintf: %s (cadena), %d (entero), %f (float).

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;

Operadores y Expresiones

Delphi usa := para asignación y = para igualdad (opuesto a lenguajes tipo C). div es división entera; / es división real (siempre devuelve Extended/Double). mod es el resto. and/or/not/xor funcionan tanto en Booleanos (lógicos) como en enteros (bit a bit) — el contexto determina cuál. shl/shr son desplazamientos de bits. Inc/Dec son incremento/decremento in-place eficientes (evite escribir A := A + 1). La concatenación de cadenas usa +. Power() está en System.Math. La distinción := vs = es la fuente #1 de errores de principiantes.

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;

Entrada, Salida y Formateo

Format() es el sprintf de Delphi — usa %s (cadena), %d (entero), %f (float), %x (hex), %m (moneda), con modificadores de ancho/precisión. WriteLn(valor:ancho:decimales) formatea floats directamente. ReadLn lee entrada en una variable. StrToInt/StrToFloat convierten cadenas a números (lanzan EConvertError en fallo); TryStrToInt devuelve un Boolean y es más seguro. IntToStr/FloatToStr convierten números a cadenas. FormatDateTime formatea fechas (yyyy, mm, dd, hh, nn, ss). FloatToStrF da control preciso (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;

Units, Alcance y Visibilidad

Las units son los módulos de Delphi. La sección interface declara lo público (visible para usuarios); implementation contiene el código y puede tener tipos/vars privados. Las secciones initialization/finalization se ejecutan al cargar/descargar la unit (como constructores/destructores para la unit). Las variables declaradas en interface son globales; en implementation son privadas de la unit. Los tipos en interface son públicos; en implementation son privados. Este diseño de dos secciones impone encapsulación a nivel de unit. La cláusula 'uses' importa otras units — resuelva conflictos de nombres con 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

Flujo de Control

If...Then...Else

If...Then...Else es el condicional de Delphi. CRÍTICO: sin punto y coma antes de 'else' — el punto y coma termina la sentencia, y else es parte del if. Para ramas con múltiples sentencias, envuelva en begin..end (aún sin punto y coma antes de else). and/or/not son operadores lógicos (también bit a bit en enteros). Use paréntesis para agrupar condiciones: (A > 0) and (B > 0). La regla de falta-de-punto-y-coma-antes-de-else es el error de sintaxis de Delphi más común para principiantes.

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;

Sentencia Case (Switch)

Case es el switch de Delphi — funciona con tipos ordinales (Integer, Char, enumeración, subrange). Cada rama puede ser un solo valor, una lista separada por comas ('D', 'F'), o un rango (1..5). La cláusula else es el default. Case NO hace fall-through (a diferencia de C). Para ramas con múltiples sentencias, use begin..end. Case es más limpio que if-else encadenados para valores discretos. No puede hacer case sobre cadenas directamente (use if-else o un lookup).

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;

Bucles For (To, Downto, In)

For...to itera ascendente; For...downto descendente. La variable del bucle no puede modificarse dentro del bucle. For...in (Delphi moderno) itera arrays, cadenas (char por char), sets y cualquier enumerable. Break sale del bucle; Continue salta a la siguiente iteración. No hay step integrado — use un condicional o un bucle while. La variable del bucle está indefinida después del bucle (no confíe en su valor). For...in es preferido para colecciones (más limpio, sin errores de índice).

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 y Repeat...Until

While prueba antes del cuerpo (puede no ejecutarse nunca); repeat...until prueba después (siempre se ejecuta al menos una vez). CRÍTICO: while continúa mientras la condición es TRUE; repeat se detiene cuando la condición es TRUE (¡lógica opuesta!). repeat...until no necesita begin..end (es inherentemente un bloque). Use while para 'cero o más veces' y repeat para 'una o más veces'. Break sale; Continue salta a la prueba. while True con Break es un idioma común para bucles con condiciones de salida complejas.

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 y Goto

With...Do accede a miembros de un record/object sin repetir la variable — útil para inicialización y reducir verbosidad. Evite With anidados (ambigüedad sobre a qué objeto pertenece un miembro). Goto salta a una etiqueta — raramente usado en Delphi moderno (prefiera Break/Continue/Exit); declare etiquetas con 'label'. Exit sale del procedimiento inmediatamente; Exit(value) devuelve un valor desde una función (sintaxis moderna). With puede hacer el código menos legible si se sobreusa — úselo con moderación para casos 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

Cadenas y Procesamiento de Texto

Tipos de Cadena y Operaciones

La cadena predeterminada de Delphi es UnicodeString (UTF-16, con conteo de referencias, copy-on-write). Las cadenas son 1-INDEXADAS (S[1] es el primer char) — una fuente común de bugs para programadores de C. Length() devuelve el conteo de chars. Pos() encuentra una subcadena (devuelve 0 si no se encuentra, no -1). Copy() extrae una subcadena (Start, Count). StringReplace reemplaza (rfReplaceAll para todas las ocurrencias). Trim/TrimLeft/TrimRight eliminan espacios en blanco. Split/Join son métodos modernos (TArray<string>). Use SameText para comparación insensible a mayúsculas.

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;

Formateo de Cadenas y Conversión

Format() es el sprintf de Delphi: %d (entero), %f (float), %s (cadena), %x (hex), %m (moneda), con modificadores de ancho/precisión. FloatToStrF da control preciso (ffFixed, ffCurrency, ffNumber, ffExponent). FormatDateTime formatea fechas: yyyy (año de 4 dígitos), mm (mes), dd (día), hh (hora), nn (minuto), ss (segundo), dddd (nombre completo del día), mmmm (nombre completo del mes). StrToInt/StrToFloat lanzan EConvertError con entrada inválida; TryStrToInt devuelve un Boolean (más seguro). Siempre use Try... para entrada del usuario.

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 y TStringList

StringBuilder (mutable) es eficiente para bucles que construyen cadenas grandes — Append modifica in place en lugar de crear nuevas cadenas. TStringList es la navaja suiza de Delphi: una lista de cadenas que puede ordenar, buscar, mantener pares clave=valor (Values[]), cargar/guardar archivos (una línea por elemento) y dividir texto delimitado (CommaText, DelimitedText). TStringList es 0-indexado (SL[0]) a diferencia de las cadenas (S[1]). Siempre envuelva en try..finally para Free. Es la forma más común de manejar archivos de texto y configs 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;

Operaciones de Char y Codificación

Char es un carácter Unicode de 2 bytes. Ord() obtiene el code point; Char() convierte de vuelta. IsDigit/IsLetter/IsWhiteSpace/IsUpper/IsLower clasifican caracteres. ToUpper/ToLower convierten mayúsculas/minúsculas. TEncoding.UTF8.GetBytes convierte cadenas a arrays de bytes (esencial para E/S de archivos y networking) — UTF-8 usa 1-4 bytes por char. TEncoding.Unicode es UTF-16 LE (siempre 2 bytes/char). Base64 (TNetEncoding.Base64) codifica datos binarios como texto para transporte. String y Char son UTF-16 internamente; convierta a UTF-8 para almacenamiento de archivos y protocolos de red.

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;

Expresiones Regulares

System.RegularExpressions proporciona TRegex para coincidencia de patrones. IsMatch prueba; Match encuentra el primero; Matches encuentra todos. Groups captura partes con paréntesis — acceda vía Groups[1], Groups[2] (1-indexado). Replace sustituye coincidencias ($1, $2 referencian grupos). Split divide en un patrón. Regex común: \d (dígito), \w (carácter de palabra), \s (espacio en blanco), + (uno+), * (cero+), {n} (exactamente n), ^/$ (inicio/fin). roCompiled compila para uso repetido más rápido. Siempre valide entrada del usuario (emails, teléfonos) con 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

Arrays, Records y Colecciones

Arrays Estáticos y Dinámicos

Los arrays estáticos tienen un tamaño fijo establecido en tiempo de compilación con un rango de índices personalizado (array[0..4] o array[1..7]). Los arrays dinámicos (array of T) son redimensionables con SetLength — son 0-indexados y con conteo de referencias. High() devuelve el último índice; Length() devuelve el conteo. SetLength en un array dinámico existente lo redimensiona (preservando valores existentes si crece). Establezca a nil para liberar. Los literales de array dinámico usan [1, 2, 3]. Los arrays dinámicos multi-dimensionales son 'arrays de arrays' (jagged) — cada fila puede tener una longitud diferente.

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;

Records (Structs)

Los records son tipos de valor (copiados en asignación, stack-allocated) — como structs en C. Los records de Delphi moderno pueden tener métodos, propiedades y visibilidad (private/public). Los records no necesitan ser liberados (sin asignación en heap). Los records variantes (case...of) crean un union donde los campos comparten memoria — útil para type tags. Use records para datos pequeños y ligeros (puntos, coordenadas, config). Use classes para objetos más grandes que necesitan herencia o polimorfismo. Los records son más rápidos (sin asignación en heap) pero no pueden heredarse.

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;

Sets y Enums

Los sets son una característica única de Delphi — una colección de valores de una enumeración o subrange (máximo 256 elementos). Operadores: + (unión), - (diferencia), * (intersección), = (igualdad), <= (subconjunto), in (pertenencia). Include/Exclude son add/remove eficientes de un solo elemento. Los sets se almacenan como bitmaps (muy rápidos). Usos comunes: TFontStyles (fsBold, fsItalic), set of Char para validación (['0'..'9']), días de la semana. Los enums son tipos ordinales — itere con Low() a High(), convierta a cadena con GetEnumName. Los sets hacen combinaciones de flags elegantes y con seguridad de tipos.

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 y Genéricos

System.Generics.Collections proporciona colecciones con seguridad de tipos: TList<T> (array dinámico), TDictionary<K,V> (hash map), TQueue<T> (FIFO), TStack<T> (LIFO), THashSet<T> (elementos únicos). Todos son genéricos (verificación de tipos en tiempo de compilación, sin casts). TList tiene Add/Remove/Delete/Sort/Contains/IndexOf. TDictionary tiene Add/Remove/TryGetValue/Keys/Values. TObjectList<T> posee sus objetos (los libera automáticamente) — úselo cuando la lista deba gestionar lifetimes de objetos. Siempre envuelva en try..finally para Free (son objetos, no records).

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;

Algoritmos de Array y Ordenación

TArray es una clase utilitaria para operaciones de arrays: Sort (con IComparer opcional personalizado), BinarySearch (búsqueda rápida en arrays ordenados), Reverse, Copy. TComparer<T>.Construct crea una función de comparación inline (método anónimo). Ordenar por un campo requiere un comparer personalizado. BinarySearch devuelve un Boolean y el índice encontrado — el array DEBE estar ordenado primero. Para búsquedas complejas, un bucle lineal con Break es simple y claro. TArray.Sort es un quicksort (O(n log n) promedio).

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

Procedimientos, Funciones y Parámetros

Procedimientos y Funciones

Los procedures (sin valor de retorno) y las functions (devuelven un valor) son las subrutinas de Delphi. La variable Result es el valor de retorno — asígnele (la función devuelve cuando termina). Exit() devuelve inmediatamente con un valor (sintaxis moderna). Las declaraciones forward le permiten llamar a una función antes de que su cuerpo esté definido (útil para recursión mutua). Las funciones pueden devolver cualquier tipo, incluyendo records, arrays y objetos. Exit sin valor simplemente sale del procedimiento. La variable Result está implícitamente declarada y coincide con el tipo de retorno.

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;

Parámetros: Const, Var, Out, Default

const: parámetro de solo lectura (también evita copiar cadenas/arrays — eficiente). var: pasar por referencia (modifica la variable del llamador — como ref en C#). out: solo salida (el llamador no inicializa; la función lo establece). Los parámetros default deben ir al final. Los parámetros open array (array of T) aceptan cualquier array o un literal [1,2,3] — use const para eficiencia. const es preferido para cadenas y arrays (sin copia); use var solo cuando necesita modificar el valor del llamador. Los open arrays son 0-indexados independientemente de los límites del array origen.

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;

Sobrecarga y Parámetros Default

La sobrecarga permite que múltiples rutinas compartan un nombre con diferentes listas de parámetros — el compilador elige la mejor coincidencia. La directiva 'overload' es obligatoria. La sobrecarga es más limpia que inventar nombres diferentes (AddInt, AddDouble). Los parámetros default son una alternativa — los llamadores pueden omitirlos. Prefiera sobrecarga cuando la lógica difiere por tipo; use defaults para valores opcionales. La ambigüedad (dos sobrecargas que coinciden igualmente) es un error de compilación. Las sobrecargas deben diferir en conteo de parámetros o tipos (el tipo de retorno solo no es suficiente).

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étodos Anónimos y Closures

Los métodos anónimos (closures) son funciones/procedimientos inline asignados a tipos 'reference to'. Capturan variables de su scope envolvente (closures). 'reference to function'/'reference to procedure' son los tipos delegate. Los métodos anónimos habilitan programación funcional: funciones de orden superior (Apply toma una función), closures (MakeMultiplier devuelve una función que recuerda Factor) y comparers personalizados (TComparer<T>.Construct). Son esenciales para ordenación genérica, event handlers y callbacks. Las variables capturadas se asignan en el heap (sobreviven a la función envolvente).

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;

Recursión y Rutinas Helper

La recursión es una función llamándose a sí misma — necesita un caso base para terminar. Factorial y Fibonacci son ejemplos clásicos. La recursión de cola (donde la llamada recursiva es la última operación) puede ser optimizada por el compilador. Los procedimientos/funciones anidados se declaran dentro de otra rutina y pueden acceder a sus variables (scoping léxico) — útiles para helpers que no necesitan ser visibles fuera. Vigile el stack overflow con recursión profunda (use iteración para entradas grandes). La memoización (cachear resultados) puede acelerar algoritmos recursivos como 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

Clases y POO

Definición de Clase, Constructor y Destructor

Las classes son tipos de referencia (heap-allocated, accedidas vía punteros). Create es el constructor; Destroy es el destructor (siempre override; llamado por Free). 'inherited' llama al método de la clase base. Los campos usan el prefijo F por convención. Las properties (property X: Type read GetX write SetX) proporcionan acceso controlado — los llamadores usan P.Age pero el setter valida. Visibilidad: private (solo unit en Delphi antiguo; strict private es verdaderamente privado), protected (subclases), public (todos), published (RTTI, para forms/inspectors). Siempre envuelva la creación de objetos en try..finally para asegurar que se llame Free.

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;

Properties e Indexed Properties

Las properties encapsulan el acceso a campos con getters/setters. Las properties de solo lectura tienen solo un especificador 'read'. La directiva 'default' hace que una indexed property sea la predeterminada — así L[i] funciona en lugar de L.Items[i]. Las properties pueden tener acceso directo a campos (read FCount) o acceso por método (read GetItem write SetItem) para validación/computación. Las indexed properties habilitan sintaxis tipo array. Las published properties (sección published) son visibles para RTTI y el form designer. Las properties son la forma de Delphi de exponer datos de forma segura — siempre prefieralas sobre campos públicos.

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;

Herencia y Polimorfismo

Herencia: TDog = class(TAnimal) significa que TDog hereda de TAnimal. 'virtual' marca un método para polimorfismo; 'override' lo reemplaza en una subclase. En runtime, el método del objeto ACTUAL se ejecuta (dispatch virtual) — llamar Speak en una referencia TAnimal que contiene un TDog llama a TDog.Speak. Los métodos static (Move) se determinan por el tipo de la variable, no del objeto. 'inherited' llama al método base. Los constructores pueden ser virtuales (patrón factory). Use virtual/override para polimorfismo; métodos static cuando el comportamiento es fijo. Siempre libere los objetos que cree.

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étodos Abstractos y Métodos de Clase

Las classes abstractas (class abstract) no pueden instanciarse — definen un contrato para subclases. Los métodos abstractos (virtual; abstract) no tienen implementación — las subclases DEBEN sobrescribirlos. Esto asegura que cada shape proporcione Area/Perimeter. Los métodos de clase (class function/procedure) no necesitan instancia — llame vía TShape.ShapeCount. Las variables de clase (class var) se comparten entre todas las instancias. El patrón Template Method: TShape.Describe llama al Area/Perimeter abstracto, que es completado por las subclases. Los métodos abstractos definen 'qué'; las subclases definen 'cómo'.

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 y Herencia Múltiple

Las interfaces son contratos puros (sin campos, sin implementación) — la forma de Delphi de lograr herencia múltiple de tipo. Una clase puede implementar muchas interfaces (TButton implementa IComparable, IDrawable, IDisposable). Las interfaces pueden tener GUIDs para QueryInterface/as casts. TInterfacedObject proporciona conteo de referencias — cuando la última referencia de interfaz sale del scope, el objeto se libera automáticamente (¡no llame Free!). Use interfaces para desacoplamiento: el código depende de IDrawable, no de TButton. El operador 'as' hace cast a una interfaz (lanza si no está soportada). Las interfaces son la columna vertebral del soporte COM de Delphi y las arquitecturas de plugins modernas.

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

Excepciones y Manejo de Errores

Try...Except...Finally

try...except captura excepciones (como try/catch en C#). Cada 'on E: ExceptionType do' maneja una excepción específica. try...finally asegura que la limpieza se ejecute sin importar las excepciones (sin manejo de excepciones — úselo para llamadas Free). El patrón es try...try...except...finally (except interno para manejo, finally externo para limpieza). 'raise' (bare) relanza la excepción actual. Exception es la clase base; EFileNotFoundException, EInOutError son subclases. Siempre ponga la excepción más específica primero y Exception (base) al final. Nunca deje un except vacío (traga errores silenciosamente).

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;

Lanzamiento y Excepciones Personalizadas

Raise crea una excepción: raise ExceptionType.Create('mensaje'). CreateFmt es como Format + Create. Las excepciones personalizadas heredan de Exception (o una subclase específica) y pueden llevar datos extra (TransactionId). Al envolver, preserve la original vía SetInner o un parámetro del constructor. Las excepciones personalizadas permiten a los llamadores capturar tipos de error específicos: capture ETransactionError separadamente de ERangeError. Siempre incluya un mensaje significativo. Built-ins comunes: 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;

Aserciones y Depuración

Assert verifica una condición y lanza EAssertionFailed si es falsa — use para invariantes (condiciones que deben ser siempre verdaderas). Las aserciones se deshabilitan con {$C-} (o se eliminan en builds release) — no las use para validación de entrada (use excepciones). OutputDebugString registra en el Event Log del IDE (sin E/S de archivos). TStopwatch mide tiempo transcurrido con precisión. Exception.StackTrace necesita info de depuración (.map file o JCLDebug/FastMM). {$IFDEF DEBUG} habilita código solo de depuración. Use aserciones para errores de lógica interna y excepciones para errores de usuario/externos.

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;

Patrones de Manejo de Excepciones

Patrones de excepción comunes: (1) Bucles de retry — envuelva una operación falible en try/except dentro de un bucle while, relanzando después de MaxRetries. (2) Valores de fallback — capture una excepción específica (EConvertError) y devuelva un default; solo trague excepciones que genuinamente espera. (3) Protección de recursos — siempre envuelva Create/Free en try/finally para que los objetos se liberen incluso en excepción (este es el idioma de Delphi más importante). (4) Múltiples recursos — anide bloques try/finally; adquiera cada recurso dentro de su propio bloque protegido. (5) Validación — lance tipos de excepción específicos (EArgumentException, ERangeError) temprano con mensajes descriptivos. Nunca capture Exception y continúe silenciosamente — como mínimo regístrelo. Prefiera try/finally para limpieza y try/except para recuperación genuina.

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;

Registro y Reporte de Errores

Un logger de producción necesita: (1) Thread safety — TCriticalSection serializa escrituras (múltiples hilos pueden registrar concurrentemente). (2) Niveles de severidad — el enum TLogLevel le permite filtrar (ej., suprimir llDebug en producción). (3) Salida formateada — DateTime + nivel + mensaje por línea, parseable después. (4) Flush después de cada escritura — para que los logs sobrevivan a crashes (escrituras bufferizadas sin flush se pierden en AV). (5) Registro de excepciones — LogException captura ClassName + Message + contexto. El patrón log-and-re-raise registra el error pero aún deja que las capas superiores lo manejen. Para logging de alto rendimiento considere colas lock-free o librerías externas (como Log4Delphi). Siempre libere el logger en finally para cerrar el file handle.

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 de Archivos y Streams

Archivos de Texto (Legacy y Moderno)

Dos enfoques: Legacy (AssignFile/Reset/Rewrite/ReadLn/WriteLn/CloseFile) es Pascal clásico — bien para E/S de texto simple pero propenso a errores (sin excepciones por defecto). Moderno (TFile en System.IOUtils) es más limpio: WriteAllText, ReadAllText, ReadAllLines, AppendAllText, Exists. Los métodos TFile lanzan excepciones en errores (use try...except). Para archivos grandes, use StreamReader/StreamWriter (línea por línea, baja memoria). Siempre cierre archivos (CloseFile para legacy, o use try..finally). TFile es preferido para código nuevo — es más seguro y consistente.

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 para Archivos y CSV

TStringList es la forma más fácil de manejar archivos de texto y CSVs simples. LoadFromFile/SaveToFile leen/escriben el archivo completo (una línea por elemento). CommaText divide/une valores separados por comas; DelimitedText usa un Delimiter personalizado. Values[] maneja pares clave=valor (como un archivo INI simple). Sorted=True auto-ordena; Find hace una búsqueda binaria (más rápido que IndexOf en listas ordenadas). Duplicates controla el comportamiento al añadir duplicados (dupIgnore, dupAccept, dupError). Para CSV complejo (campos entre comillas con comas), use un parser CSV dedicado. TStringList es 0-indexado.

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;

Streams y E/S Binaria

TFileStream es E/S de bytes de bajo nivel (Read/Write buffers, Position para seeking). TBinaryWriter/Reader escriben/leen valores tipados (Int32, Double, String, Boolean) — el orden de lectura debe coincidir con el de escritura. TStreamReader/Writer manejan texto con codificación (UTF-8, ASCII, Unicode) — úselos para archivos de texto con caracteres no-ASCII. Todos los streams deben liberarse (try..finally). fmCreate crea/sobrescribe; fmOpenRead abre solo lectura; fmOpenWrite abre para escritura. Para archivos grandes, lea línea por línea con StreamReader (baja memoria) en lugar de LoadFromFile (carga el archivo completo).

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;

Operaciones de Directorio y Path

System.IOUtils proporciona TPath, TFile, TDirectory para operaciones de archivos modernas. TPath.Combine une paths de forma segura (cross-platform). TPath.GetTempFileName crea un archivo temporal único. TDirectory.GetFiles soporta patrones de búsqueda y búsqueda recursiva (soAllDirectories). TFile.Copy/Move/Delete son operaciones simples de archivos. TFileInfo da metadatos de archivos (tamaño, timestamps). Siempre use métodos TPath en lugar de concatenación de cadenas para paths (maneja separadores correctamente). Estas classes funcionan en Windows, macOS y 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;

Archivos INI y JSON

TIniFile lee/escribe archivos de configuración INI (secciones en [corchetes], clave=valor). ReadString/ReadInteger/ReadBool tienen valores default (devueltos si la clave falta). Los archivos INI son configs simples y legibles por humanos — buenos para preferencias de usuario. Para datos estructurados, use JSON (System.JSON). TJSONObject construye/parsea objetos JSON; TJSONArray para arrays. AddPair añade clave-valor; GetValue<T> recupera valores tipados. ParseJSONValue parsea una cadena JSON. JSON es ideal para APIs, configs complejos e intercambio de datos. Para clientes REST, use TRESTClient o componentes 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

Componentes VCL en Profundidad

Ciclo de Vida de Form y Component

Los forms VCL siguen un ciclo de vida estricto: OnCreate (asignar recursos, inicializar) → OnShow (el form se hace visible) → OnActivate → OnResize → OnPaint → ... → OnCloseQuery (puede cancelar el cierre) → OnClose → OnDestroy (liberar recursos). Siempre empareje OnCreate con OnDestroy para gestión de recursos. OnCloseQuery le permite prevenir el cierre (establezca CanClose := False). Sender es el componente que disparó el evento. Los componentes poseen sus hijos — liberar un form libera todos sus componentes automáticamente.

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;

Controles VCL Comunes

VCL proporciona un conjunto rico de controles: TEdit (texto de una línea), TMemo (texto multi-línea), TLabel (texto no editable), TButton, TCheckBox, TRadioButton, TComboBox (dropdown), TListBox (lista seleccionable). TStrings es la colección base (Lines, Items son TStrings). ItemIndex selecciona elementos (0-based, -1 = ninguno). Estilos de ComboBox: csDropDown (editable), csDropDownList (solo lectura). RadioGroup agrupa radio buttons con un ItemIndex. Sorted auto-ordena elementos. PasswordChar enmascara entrada en 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 y DBGrid

TStringGrid muestra datos tabulares en una grid tipo hoja de cálculo. Cells[Col, Row] accede a celdas individuales (0-indexado). FixedRows/FixedCols crean headers no desplazables. ColWidths/RowHeights personalizan tamaños. Options como goEditing (celdas editables), goColSizing (redimensionar columnas), goRowSelect habilitan comportamientos. OnDrawCell permite renderizado personalizado con el Canvas. TDBGrid se conecta directamente a un DataSet (TTable, TQuery) vía un TDataSource — automáticamente muestra y edita registros de base de datos. Use TDBGrid para datos de base de datos, TStringGrid para datos en memoria.

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 y TListView

TTreeView muestra datos jerárquicos (árbol) usando objetos TTreeNode. AddChild crea nodos anidados. Expand(True) expande recursivamente. GetNext recorre depth-first; GetNextSibling recorre nivel por nivel. BeginUpdate/EndUpdate agrupan cambios para rendimiento. TListView muestra elementos en varios estilos de vista: vsIcon, vsSmallIcon, vsList, vsReport (columnas). Caption es la primera columna; SubItems contiene las columnas siguientes. Ambos soportan modo owner-data (virtual) para datasets grandes vía eventos 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');

Diálogos y Componentes Comunes

Delphi proporciona componentes de diálogo estándar: TOpenDialog/TSaveDialog (selección de archivos), TOpenPictureDialog (vista previa de imágenes), TColorDialog, TFontDialog, TPrintDialog. Execute devuelve True si el usuario hizo clic en OK. Filter establece patrones de tipo de archivo ('Descripción|*.ext'). MessageDlg muestra cajas de mensajes modales con tipos (mtInformation, mtWarning, mtError, mtConfirmation) y conjuntos de botones ([mbYes, mbNo, mbOK, mbCancel]). InputBox/InputQuery obtienen entrada de texto del usuario. TPageControl gestiona interfaces con pestañas con páginas TTabSheet. Todos los diálogos son componentes no visuales colocados en el form.

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

Programación Orientada a Eventos

Eventos y Event Handlers

Los eventos en Delphi son punteros a métodos (procedure of object). TNotifyEvent es el tipo de evento estándar: procedure(Sender: TObject) of object. Los eventos son properties — asigne handlers en tiempo de diseño (Object Inspector) o runtime. Siempre verifique Assigned() antes de llamar a un event handler (puede ser nil si no asignado). Sender es el objeto que disparó el evento. Los eventos personalizados usan 'of object' para enlazar a métodos de instancia. Los parámetros var (como var Key: Char en OnKeyPress) permiten a los handlers modificar valores — establezca Key := #0 para suprimir entrada.

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;

Delegates y Punteros a Método

Los punteros a método ('of object') llevan tanto la dirección del método como la instancia del objeto — son closures sobre Self. Los punteros a procedure regulares (sin 'of object') apuntan a funciones standalone. Los punteros a método habilitan callbacks, patrones strategy y sistemas de eventos. Asignar Op := Calc.Add almacena la referencia; llamar Op(10, 20) invoca Calc.Add en la instancia Calc. Los métodos anónimos (reference to function) son una alternativa moderna con semántica de closure. Los punteros a método son la columna vertebral de la arquitectura VCL/FMX orientada a eventos 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étodos Anónimos y Closures

Los métodos anónimos (reference to function/procedure) son closures inline que capturan variables de su scope envolvente. Los tipos 'reference to' son la alternativa moderna a los punteros a método — capturan variables por referencia, así que los cambios a variables capturadas afectan el closure. Esto habilita patrones funcionales: map/filter/reduce, callbacks y ejecución diferida. TFunc<T,TResult> y TProc<T> son alias genéricos en System.SysUtils. Los métodos anónimos son esenciales para programación paralela (PPL) e idiomas modernos de Delphi. Las variables capturadas sobreviven a su scope de declaración.

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;

Manejo de Mensajes y Mensajes de Windows

VCL está construido sobre mensajes de Windows. La directiva 'message' maneja mensajes específicos (WM_LBUTTONDOWN, WM_KEYDOWN, etc.). Los records de mensaje (TWMMouse, TWMKeyDown) son overlays tipados sobre TMessage. Siempre llame a inherited para permitir el procesamiento por defecto (a menos que quiera suprimir el mensaje). WndProc intercepta TODOS los mensajes antes del dispatch — use con moderación para concerns transversales. PostMessage es asíncrono (devuelve inmediatamente); SendMessage es sincrónico (espera al handler). WM_USER + N define mensajes personalizados. Esta es la base del modelo orientado a eventos de 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

Eventos de Aplicación y Procesamiento Idle

TApplicationEvents centraliza eventos a nivel de app: OnIdle (se dispara cuando la cola de mensajes está vacía), OnException (handler global de excepciones), OnMinimize/OnRestore, OnHint (hints de status bar), OnMessage (todos los mensajes de Windows). OnIdle con Done := False crea un bucle ajustado; use con cuidado. TTimer dispara OnTimer a intervalos (Interval en ms) — está basado en mensajes, así que no disparará durante operaciones bloqueantes. Application.ProcessMessages bombea la cola de mensajes durante operaciones largas (previene 'Not Responding') pero puede causar bugs de reentrada. TThread.Queue/Synchronize marshalan actualizaciones de UI desde hilos en background al hilo 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

Acceso a Base de Datos con FireDAC

Conexión y Fundamentos de Query

FireDAC es el framework moderno de acceso a datos universal de Delphi que soporta SQLite, PostgreSQL, MySQL, SQL Server, Oracle, InterBase y más. TFDConnection gestiona la conexión a la base de datos (establezca DriverName y Params). TFDQuery ejecuta SQL con parámetros (sintaxis :param) — siempre use parámetros para prevenir inyección SQL. ExecSQL ejecuta INSERT/UPDATE/DELETE/DDL (sin result set); Open ejecuta SELECT (devuelve un cursor). FieldByName('col').AsString/AsInteger lee valores. Navegue con Next/Prev/First/Last; Eof marca el final. FireDAC reemplaza las tecnologías dbExpress y BDE más antiguas.

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 y Live Bindings

TFDTable abre una tabla completa (SELECT * FROM tablename) — conveniente para CRUD simple pero menos eficiente que TFDQuery para tablas grandes. Navegación de dataset: First/Next/Prior/Last/MoveBy. Locate busca por valores de campo (devuelve True si se encuentra). Edición: Append/Insert (nueva fila) o Edit (existente), luego establezca campos, luego Post (commit) o Cancel (revertir). Filter restringe filas visibles (client-side). IndexFieldNames ordena registros. Conecte TFDTable/TFDQuery a TDataSource, luego a TDBGrid/TDBEdit para UI data-aware automática. Live Bindings (FMX) proporcionan binding visual de controles a campos de datos.

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;

Transacciones y Operaciones por Lote

Las transacciones aseguran atomicidad — todas las operaciones tienen éxito o ninguna. StartTransaction/Commit/Rollback envuelven operaciones relacionadas. Sin transacciones explícitas, FireDAC auto-confirma cada sentencia (lento para inserts masivos). Array DML (Execute(count, startAt)) envía lotes parametrizados en un solo round-trip — dramáticamente más rápido para inserts masivos (10-100x speedup). Siempre envuelva transacciones en try/except para Rollback en fallo. Para transacciones largas, considere niveles de aislamiento (xiReadCommitted, xiRepeatableRead). El pool de conexiones (TFDManager) mejora el rendimiento multi-hilo.

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;

Procedimientos Almacenados y Metadatos

TFDStoredProc llama procedimientos almacenados de base de datos. Establezca StoredProcName y parámetros (ParamType: ptInput, ptOutput, ptInputOutput, ptResult). ExecProc ejecuta procedimientos que no devuelven cursores; Open ejecuta los que devuelven result sets. Los procedimientos almacenados encapsulan lógica de negocio server-side para rendimiento y seguridad. TFDMetaInfoQuery consulta el esquema de base de datos (tablas, columnas, índices, constraints) — útil para construir herramientas dinámicas, ORMs o navegadores de esquema. Opciones MetaInfoKind: mkTables, mkColumns, mkIndexes, mkPrimaryKey, mkForeignKeys. FireDAC también soporta caché de esquema para acceso a metadatos offline.

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;

FireDAC Memory Table y Local SQL

TFDMemTable es un dataset en memoria — perfecto para caché, datos temporales y pruebas unitarias sin base de datos. Defina campos con FieldDefs, luego CreateDataSet. AppendRecord añade filas. Soporta índices, filtros y toda la navegación de dataset. Local SQL (TFDLocalSQL) le permite ejecutar consultas SQL contra cualquier TDataSet (incluyendo TFDMemTable, TClientDataSet, incluso Excel vía ODBC) — habilitando joins entre tablas en memoria y tablas de base de datos. Esto es poderoso para ETL, reporting y construcción de capas de datos que funcionan offline. TFDMemTable también puede cargar/guardar a archivos binarios o JSON para persistencia.

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

Genéricos y Métodos Anónimos

Clases y Métodos Genéricos

Los genéricos (introducidos en Delphi 2009) habilitan contenedores y algoritmos con seguridad de tipos. TStack<T> funciona con cualquier tipo T — el compilador genera versiones especializadas. Esto elimina casts de runtime (sin casting a TObject) y captura errores de tipos en tiempo de compilación. Los parámetros de tipo genérico usan sintaxis <T>. Se soportan métodos, classes, records e interfaces genéricos. Las constraints (class, constructor, interface) restringen qué tipos pueden usarse. La RTL proporciona TList<T>, TDictionary<TKey,TValue>, TQueue<T>, TStack<T>, TObjectList<T> en 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;

Constraints Genéricos

Las constraints genéricas restringen los parámetros de tipo: 'class' (debe ser un tipo class), 'constructor' (debe tener un constructor Create sin parámetros — habilita T.Create), 'record' (debe ser un tipo de valor), nombres de interface (debe implementar la interface). Múltiples constraints se separan por comas. Las constraints habilitan llamar métodos en T (ej., T.Create con la constraint constructor). Sin constraints, solo puede asignar/comparar T (sin llamadas a métodos). La inferencia de tipos a veces le permite omitir parámetros de tipo explícitos. Las constraints son esenciales para construir frameworks y ORMs con seguridad de tipos.

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> y TDictionary<TKey,TValue>

System.Generics.Collections proporciona contenedores con seguridad de tipos: TList<T> (array dinámico), TDictionary<TKey,TValue> (hash map), TQueue<T> (FIFO), TStack<T> (LIFO), TObjectList<T> (posee sus objetos — los libera automáticamente). Sort usa comparación por defecto; TComparer<T>.Construct crea comparers personalizados con métodos anónimos. FindIndex/búsqueda basada en Predicate usa predicados de función anónima. TryGetValue devuelve True y devuelve el valor si se encuentra (evita excepción). AddOrSetValue actualiza o inserta. TObjectList<T> con OwnsObjects := True libera automáticamente los objetos contenidos cuando la lista se libera — previniendo fugas de memoria.

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étodos Anónimos como Callbacks

Los métodos anónimos habilitan programación funcional en Delphi. Los tipos 'reference to function' son closures — capturan variables de su scope envolvente. Las funciones de orden superior como Map y Filter toman funciones como parámetros, habilitando transformaciones de datos concisas. TFunc<T,TResult> y TProc<T> son tipos delegate genéricos integrados. Los closures capturan variables por referencia, así que reflejan cambios posteriores. Este patrón reemplaza verbosas interfaces de callback y es esencial para PPL (Parallel Programming Library), event handlers y operaciones estilo LINQ. Los métodos anónimos son reference-counted y gestionados automáticamente.

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 Genéricas y TComparer

Las interfaces genéricas habilitan contratos con seguridad de tipos: IRepository<T> funciona con cualquier tipo de entidad. Combinado con conteo de referencias (TInterfacedObject), esto proporciona gestión automática de memoria — las interfaces son reference-counted, liberadas cuando la última referencia cae. TComparer<T>.Construct crea un IComparer<T> desde una función de comparación anónima — usado por Sort, BinarySearch y SortedDictionary. Las interfaces genéricas son la base de la inyección de dependencias en Delphi (registre IRepository<TUser>, inyecte en servicios). El framework Spring4D extiende esto con un contenedor DI completo. Las constraints genéricas (class, constructor) aseguran que T pueda instanciarse.

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 y Reflection

Fundamentos de RTTI Extendido

El RTTI Extendido (Runtime Type Information), introducido en Delphi 2010, proporciona reflection completo: inspeccionar tipos, propiedades, métodos y campos en runtime. TRTTIContext es el punto de entrada. GetType devuelve TRttiType para una clase. GetProperties enumera propiedades published. GetValue/SetValue leen/escriben valores de propiedades dinámicamente usando TValue (un tipo tipo-variant). Solo los miembros 'published' tienen RTTI por defecto (use la directiva {$RTTI EXPLICIT ...} para más). RTTI potencia la serialización (JSON/XML), ORMs, inyección de dependencias y diseñadores visuales. Tiene una pequeña sobrecarga de rendimiento pero habilita metaprogramación poderosa.

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;

Invocación de Métodos y Atributos

RTTI puede invocar métodos dinámicamente vía TRttiMethod.Invoke — pase argumentos como array de TValue. Los atributos (subclases de TCustomAttribute) adjuntan metadatos a tipos, propiedades y métodos usando sintaxis [Attribute]. GetAttributes los recupera en runtime. Esto habilita frameworks de validación ([Required], [MaxLength]), mapeo ORM ([Table], [Column]) y control de serialización ([JsonProperty]). Los atributos son una característica de metaprogramación poderosa — el compilador los almacena en RTTI, y los frameworks los leen para dirigir el comportamiento. La invocación de métodos vía RTTI es más lenta que llamadas directas pero esencial para scripting, DI y dispatch dinámico.

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;

Descubrimiento y Enumeración de Tipos

TRTTIContext.GetTypes enumera todos los tipos con RTTI en el programa compilado — útil para descubrimiento de plugins, escaneo de modelos ORM y construcción de navegadores de tipos. FindType localiza un tipo por nombre calificado ('UnitName.TypeName'). TRttiType proporciona GetFields (todos los campos), GetMethods (todos los métodos), GetProperties (propiedades published). TypeKind distingue classes, records, interfaces, enums, etc. AsInstance.MetaclassType da la referencia de clase para instanciación. Esto habilita frameworks que auto-descubren y conectan componentes. Los frameworks Spring4D y DORM usan esto para mapeo ORM automático. La enumeración RTTI es lenta — almacene en caché los resultados para uso repetido.

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 y Tipado Dinámico

TValue es el tipo de valor dinámico de Delphi — un tagged union que mantiene cualquier tipo con su información de tipo. From<T> envuelve un valor; AsType<T>/AsInteger/AsString lo desenvuelve. IsType<T> verifica el tipo. TryAsType intenta conversión segura. TValue es esencial para RTTI (valores de propiedades, argumentos de métodos) y habilita tipado dinámico en un lenguaje de tipado estático. Es similar al 'object' de C# con info de tipo, o a la naturaleza dinámica de Python. TValue maneja primitivos, cadenas, objetos, arrays y records. Úselo cuando construya serializadores, motores de script o capas de datos genéricas. Tiene sobrecarga vs. tipado directo pero proporciona máxima flexibilidad.

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;

Serialización con RTTI

RTTI habilita serialización automática — convertir objetos a/desde JSON, XML o cualquier formato sin código de mapeo manual. ObjectToJSON itera propiedades published, lee valores vía RTTI y construye un TJSONObject. JSONToObject invierte el proceso. Este patrón potencia clientes REST, sistemas de configuración y capas ORM. La unit REST.Json proporciona TJson.ObjectToJsonString y TJson.JsonToObject para esto out of the box. Para uso en producción, añada atributos ([JsonProperty('name')]) para controlar nombres de campos y maneje objetos anidados, arrays y tipos personalizados. La serialización basada en RTTI es más lenta que mappers escritos a mano pero mucho más mantenible.

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 y COM

Fundamentos de Interfaces y Conteo de Referencias

Las interfaces definen contratos (firmas de métodos) sin implementación. TInterfacedObject proporciona conteo de referencias — cuando la última referencia de interfaz cae, el objeto se libera automáticamente (sin necesidad de llamar Free). Esta es la gestión automática de memoria de Delphi para objetos de interfaz. Los GUIDs (['{...}']) habilitan interop COM y verificaciones InterfaceAs/Supports. Una clase puede implementar múltiples interfaces (TShape implementa tanto IMovable como IDrawable). Las propiedades de interfaz están permitidas (deben tener métodos read/write). Siempre use tipos de interfaz (IMovable) no tipos de clase (TShape) para que el conteo de referencias funcione. Mezclar referencias de objeto e interfaz puede causar liberación prematura.

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;

Inyección de Dependencias con Interfaces

Las interfaces habilitan Inyección de Dependencias — pase dependencias (ILogger, IUserDataAccess) a través de constructores en lugar de hardcodearlas. Esto desacopla TUserService de implementaciones concretas: cambie TConsoleLogger por TFileLogger sin modificar TUserService. TUserService mismo no es ref-counted (hereda de TObject, no TInterfacedObject) así que necesita Free manual. Para DI completo, use un contenedor (Spring4D, DSharp) que resuelve dependencias por tipo de interfaz: Container.RegisterType<ILogger, TConsoleLogger>; Container.Build; Service := Container.Resolve<TUserService>. DI mejora la testabilidad (inyecte mocks), mantenibilidad y modularidad. Siempre dependa de abstracciones (interfaces), no de concreciones.

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 con COM

COM (Component Object Model) permite a Delphi interactuar con aplicaciones y librerías de Windows. CreateOleObject crea objetos COM vía late binding (tipo Variant — sin verificación en tiempo de compilación, pero simple). Import Type Library genera units early-bound con interfaces tipadas (IntelliSense, verificación de tipos, mejor rendimiento). IUnknown es la interfaz base de COM con AddRef/Release/QueryInterface para conteo de referencias. stdcall es la convención de llamada COM. CoCreateInstance es la API de bajo nivel. Usos comunes de COM: automatización de Office (Excel, Word), ADO (base de datos), integración con shell, consultas WMI. Siempre llame CoInitialize antes de operaciones COM en hilos. Los objetos COM son apartment-threaded — haga marshal entre hilos con cuidado.

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 y Agregación

La directiva 'implements' delega una interfaz a una propiedad — composición sobre herencia. TDataService expone ICache delegando a FCache (un TMemoryCache). Esto es más limpio que heredar y le permite mezclar y combinar comportamientos. Supports() verifica si un objeto implementa una interfaz (usa QueryInterface internamente). El operador As realiza un cast de interfaz verificado. La delegación de interfaz habilita el patrón decorator (envolver un cache con logging), patrón strategy (intercambiar implementaciones de cache) y separación limpia de concerns. QueryInterface de COM es el mecanismo subyacente — cada objeto interfaced puede consultarse por cualquier interfaz que soporte.

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;

Referencias Weak y Unsafe

El conteo de referencias puede causar fugas de memoria con referencias circulares (parent↔child). [Weak] rompe ciclos — rastrea la referencia pero no incrementa el ref count, y se nillea automáticamente cuando el objetivo se libera. [Unsafe] es un puntero crudo (sin tracking, sin ref count) — más rápido pero peligroso (punteros colgantes). Use [Weak] para referencias parent/back, patrones observer y suscripciones a eventos. La referencia strong por defecto (predeterminada) incrementa el ref count y mantiene el objeto vivo. El ARC de Delphi (obsoleto en favor de [Weak]) solía manejar esto automáticamente en móvil. En desktop, las interfaces usan conteo manual de referencias — [Weak] es esencial para diseños sin ciclos. Siempre empareje referencias strong y weak correctamente.

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

Multihilo y PPL

Fundamentos de TThread

TThread es la base del multihilo de Delphi. Sobrescriba Execute con el trabajo en background. Verifique Terminated periódicamente para cancelación graceful. TThread.Synchronize ejecuta código en el hilo principal (bloqueante — espera a que complete); TThread.Queue es asíncrono (posta y devuelve inmediatamente). NUNCA acceda a controles de UI desde hilos en background — siempre use Synchronize o Queue. FreeOnTerminate := True auto-libera el hilo cuando Execute termina. CreateAnonymousThread crea un hilo one-shot desde un método anónimo — conveniente para tareas simples. Para código de producción, prefiera PPL (TTask) sobre TThread crudo para mejor composición y manejo de errores.

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;

Parallel Programming Library (PPL)

La Parallel Programming Library (PPL) en System.Threading proporciona concurrencia de alto nivel: TTask (async fire-and-forget), TTask.Future<T> (async con valor de retorno) y bucles paralelos. Las tasks usan el thread pool automáticamente — sin necesidad de gestionar hilos. WaitForAll/WaitForAny componen múltiples tasks. Future.Value bloquea hasta que el resultado está listo (como una promise). PPL es la alternativa moderna a TThread crudo — más limpia, componible y se integra con patrones async/await. Las tasks capturan excepciones y las relanzan cuando accede a .Value, habilitando propagación proper de errores. Use TEvent/TCountdownEvent para sincronización fine-grained entre tasks.

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 y Bucles

TParallel.For paraleliza bucles a través del thread pool — las iteraciones se ejecutan concurrentemente en múltiples cores. Use &For (palabra clave escapada) ya que 'for' está reservada. Para bucles CPU-bound con iteraciones independientes, esto puede dar speedup casi lineal en máquinas multi-core. CRÍTICO: el estado compartido (como Sum) debe protegerse con locks (TCriticalSection) o use TInterlocked.Increment para operaciones atómicas. State.Break detiene el bucle (como break). State.ShouldExit verifica si Break fue llamado. Evite paralelizar bucles con pocas iteraciones o E/S pesada (el thread pool se agota). Stride controla el stepping de iteración. Los bucles paralelos anidados raramente ayudan — el bucle externo ya satura los cores.

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);

Primitivas de Sincronización

System.SyncObjs proporciona primitivas de sincronización: TCriticalSection (mutex — solo un hilo entra a la vez), TEvent (señal entre hilos — SetEvent despierta, WaitFor bloquea), TMonitor (lock cualquier objeto — como monitores de Java/C# con Wait/Pulse), TInterlocked (Increment/Decrement/Exchange/CompareExchange atómicos — lock-free). TCriticalSection es el más común — siempre empareje Enter/Leave con try/finally. TEvent.WaitFor devuelve wrSignaled, wrTimeout o wrAbandoned. TMonitor.Wait libera temporalmente el lock y bloquea; Pulse/PulseAll despiertan waiters. TInterlocked es el más rápido para contadores simples — sin overhead de lock. Elija la primitiva correcta: CriticalSection para acceso exclusivo, Event para señalización, Interlocked para contadores atómicos.

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);

Thread Pool y Patrón Async/Await

TThreadPool gestiona un pool de hilos worker — reusar hilos evita el overhead de creación. Establezca min/max threads según su workload (CPU-bound: ~conteo de cores, I/O-bound: más). TTask.Run es abreviatura de Create+Start. ContinueWith encadena tasks — se ejecuta después de que el antecedente completa, habilitando pipelines. Task.Status (Created, WaitingToRun, Running, Completed, Canceled, Faulted) rastrea el ciclo de vida. ICancellation habilita cancelación cooperativa — verifique IsCancelled periódicamente en tasks largas. Para true async/await, Delphi no tiene await a nivel de lenguaje, pero TTask.Future + .Value proporciona semántica equivalente. OmniThreadLibrary (OTL) ofrece abstracciones de más alto nivel (pipelines, message passing) construidas sobre 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

Programación de Red con Indy

Cliente y Servidor TCP (Indy)

Indy (Internet Direct) es la librería de networking integrada de Delphi. TIdTCPClient se conecta a servidores — WriteLn/ReadLn para protocolos basados en líneas, Write/Read para binario. ConnectTimeout previene colgar. TIdTCPServer escucha conexiones — OnExecute se ejecuta en un hilo por cliente (AContext representa cada conexión). Indy usa sockets bloqueantes (modelo más simple — sin callbacks), así que los handlers del servidor se ejecutan en hilos worker. Siempre maneje desconexiones de forma graceful. Para servidores de alto rendimiento, considere ICS (E/S overlapped) o Synapse. Los componentes Indy son no-visuales — coloque en un form o cree en código. Establezca Active := True para empezar a escuchar. DefaultPort establece el puerto de escucha.

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;

Cliente HTTP (TIdHTTP)

TIdHTTP es el cliente HTTP de Indy — soporta GET, POST, PUT, DELETE, headers, cookies y SSL/TLS. Para HTTPS, adjunte TIdSSLIOHandlerSocketOpenSSL (requiere DLLs de OpenSSL: libeay32/ssleay32 o libcrypto/libssl). Request.ContentType y CustomHeaders establecen metadatos de la petición. POST acepta un body de cadena (para JSON/APIs) o TStrings (para datos de formularios). EIdHTTPProtocolException captura errores HTTP (404, 500, etc.) con ErrorCode y ErrorMessage. Para clientes REST modernos, considere TRESTClient (integrado, sin dependencia de OpenSSL) o TNetHTTPClient (más ligero). Siempre libere HTTP y el handler SSL en bloques finally. Establezca Http.HandleRedirects := True para seguir redirecciones 301/302 automáticamente.

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 envía email vía servidores SMTP. TIdMessage representa el email (From, Recipients, Subject, Body). Para Gmail/Office365, use TLS (Puerto 587, utUseExplicitTLS) o SSL (Puerto 465, utUseImplicitTLS). Gmail requiere una 'App Password' (no su contraseña regular) con 2FA habilitado. TIdAttachmentFile añade adjuntos de archivo. Para emails HTML, establezca ContentType := 'text/html'. Para multipart (HTML + texto plano + adjuntos), use TIdMessageBuilderHTML. Puertos comunes: 25 (sin encriptar/relay), 465 (SSL), 587 (STARTTLS). Siempre envuelva Connect/Send en try/finally para asegurar Disconnect. Para recibir email, use TIdPOP3 o 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 y Sockets Crudos

UDP es connectionless — sin handshake, sin entrega garantizada, pero más rápido que TCP. TIdUDPClient.Send dispara datagramas; ReceiveString espera respuestas con timeout. BroadcastEnabled envía a 255.255.255.255 (todos los dispositivos en LAN) — útil para descubrimiento de servicios. TIdUDPServer.OnUDPRead recibe datagramas; ABinding.PeerIP/PeerPort identifican al remitente. UDP es ideal para: DNS, SNMP, actualizaciones de estado de juegos, streaming media y protocolos de descubrimiento. Para fiabilidad sobre UDP, implemente ACK/retry a nivel de aplicación. TIdBytes es el tipo de array de bytes de Indy — use BytesToString/ToBytes para conversión. Para control de socket crudo (paquetes IP crudos, protocolos personalizados), use la unit WinSock2 o la librería 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 y Cliente REST

TIdFTP proporciona funcionalidad de cliente FTP — Connect, List, Put (upload), Get (download), MakeDir, ChangeDir. Modo pasivo (Passive := True) funciona a través de NAT/firewalls. UseTLS asegura FTP (FTPS). Para SFTP (basado en SSH), use una librería de terceros (libssh2, SecureBlackbox). TRESTClient/TRESTRequest/TRESTResponse son componentes REST integrados (sin dependencia de OpenSSL) — ideales para consumo moderno de APIs. Resource usa placeholders {param} rellenados por AddUrlSegment. Execute envía la petición; RESTResponse.Content contiene el body; JSONValue parsea JSON automáticamente. Los componentes REST soportan OAuth2, basic auth y authenticators personalizados. Para REST de alto rendimiento, considere TNetHTTPClient (más ligero) o TIdHTTP de Indy para máximo control.

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

DLL y Paquetes BPL

Creación y Uso de DLLs

Las DLLs (Dynamic Link Libraries) comparten código entre aplicaciones. Use la palabra clave 'library' (no 'program') para construir una DLL. 'exports' lista funciones disponibles para llamadores externos. stdcall es la convención de llamada estándar de Windows (compatible con C/C++, VB, C#). Import estático (external) enlaza en tiempo de compilación — la DLL debe existir en runtime. Carga dinámica (LoadLibrary/GetProcAddress) carga en runtime — habilita plugins y características opcionales. FreeLibrary descarga la DLL. PChar (PWideChar) es el tipo de cadena estándar para exports de DLL (memoria compartida, sin tipos específicos de Delphi). NUNCA exporte cadenas, objetos o interfaces de Delphi directamente — son internos de Delphi. Use la unit ShareMem para compartir cadenas Delphi-to-Delphi (requiere 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;

Compartir Objetos vía Interfaces

Compartir objetos a través de límites de DLL es complicado — las classes de Delphi no pueden exportarse directamente (diferentes gestores de memoria, diferente RTTI). La solución: use interfaces con GUIDs. La DLL exporta una función factory (CreatePlugin) que devuelve un IPlugin. La app host define la misma interfaz (¡mismo GUID!) y llama al factory. El conteo de referencias de interfaz maneja la limpieza automáticamente. Use PChar para cadenas (no cadena Delphi) para evitar conflictos de gestor de memoria. Este es el patrón de arquitectura de plugins — cargue DLLs dinámicamente, cree plugins vía factory, comunique vía interfaces. Para sistemas de plugins completos, considere el plugin framework de Delphi o use packages (BPL) que comparten el RTL y permiten compartir classes directamente.

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;

Paquetes BPL (Borland Package Library)

Los BPLs (Borland Package Libraries) son librerías compartidas específicas de Delphi — comparten el RTL de Delphi, permitiendo compartir classes/objetos directamente (a diferencia de las DLLs). Construya con la palabra clave 'package'. Los runtime packages reducen el tamaño del EXE (código compartido en archivos .bpl) y habilitan módulos hot-swappable. LoadPackage/UnloadPackage cargan BPLs dinámicamente — GetClass encuentra classes registradas por nombre. RegisterClass/UnRegisterClass hacen las classes descubribles. Los BPLs requieren que los BPLs del RTL de Delphi (rtl.bpl, vcl.bpl) se desplieguen. Use BPLs para: arquitecturas de plugins (compartir tipos Delphi directamente), aplicaciones modulares (cargar características on demand) y reducir memoria (código compartido cargado una vez). Para compartir cross-language, use DLLs; para Delphi-only, los BPLs son más poderosos.

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.

Gestión de Memoria a Través de Límites

El problema #1 de DLL: liberar memoria en un módulo que fue asignada en otro. Cada módulo tiene su propio gestor de memoria — mezclarlos causa corrupción de heap y crashes. Soluciones: (1) ShareMem — comparte BorlndMM.dll, pero requiere desplegar esa DLL. (2) Patrón caller-allocates — el llamador proporciona el buffer, la DLL lo llena (más seguro, language-agnostic). (3) SimpleShareMem/FastMM — gestor de memoria compartida moderno (FastMM es default desde Delphi 2006). (4) Liberación basada en callback — la DLL proporciona una función free. Para returns PChar, use StrNew/StrDispose (API de Windows, compartido). Para producción Delphi-to-Delphi, use BPLs (RTL compartido) o SimpleShareMem. Para cross-language, siempre use el patrón caller-allocates. Nunca pase tipos cadena/objeto/interfaz de Delphi a través de límites de DLL sin un gestor de memoria compartido.

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;

Archivos de Recursos y Embedding

Los archivos de recursos embeben datos binarios (imágenes, iconos, sonidos, cadenas, info de versión) en el EXE/DLL — sin archivos externos. Cree un script .rc, compile con brcc32 (o deje que el IDE auto-compile). {$R file.res} lo enlaza. TResourceStream lee recursos RCDATA como un stream. LoadIcon/LoadString usan la API de Windows para tipos de recursos específicos. Los recursos son de solo lectura en runtime pero mantienen todo en un archivo (genial para despliegue). Usos comunes: iconos de aplicación, imágenes de splash screen, config default, sonidos WAV, info de versión (diálogo de propiedades del archivo), cadenas localizadas. Para datos grandes, considere comprimir antes de embeber. Los IDs de recurso pueden ser nombres (cadenas) o números. RT_RCDATA es el tipo de recurso binario genérico.

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

Depuración y Optimización de Rendimiento

Depurador y Breakpoints

El depurador del IDE de Delphi es poderoso: establezca breakpoints haciendo clic en el gutter. Los breakpoints condicionales rompen solo cuando una expresión es verdadera (ej., i > 100). Los breakpoints log/trace registran mensajes sin detener — geniales para monitorear bucles. asm int 3 end crea un hard breakpoint en código (CPU trap). OutputDebugString registra en la ventana Event Log (y herramienta DebugView). Assert verifica condiciones en builds debug (deshabilitado con {$C-} o aserciones off en release). DebugHook es non-zero cuando se ejecuta en el IDE. La ventana Call Stack rastrea la cadena de llamadas; la ventana Threads inspecciona todos los hilos; Local Variables muestra el scope actual. Habilite 'Use Debug DCUs' para hacer step into del código fuente 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

Manejo de Excepciones y Stack Traces

Excepciones de Delphi: try/except captura errores, try/finally garantiza limpieza. Las classes de excepción forman una jerarquía: Exception → EDivByZero, EAccessViolation, EListError, EAbort (silencioso), etc. 'on E: ExceptionType do' captura tipos específicos; el base 'on E: Exception do' captura todos. 'raise;' relanza la excepción actual (preserva stack trace). EAbort (o procedimiento Abort) lanza una excepción silenciosa (sin diálogo). TApplicationEvents.OnException es el handler global — captura excepciones no manejadas. Para stack traces, use JCL (JclDebug) o MadExcept/ExceptionHunter — capturan call stacks, dumps de registros e incluso envían reportes de crash por email. Siempre registre excepciones para depuración post-mortem. Nunca trague excepciones silenciosamente en producción.

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

Profiling y Rendimiento

TStopwatch es el timer de alta precisión (usa QueryPerformanceCounter). Siempre haga benchmark antes de optimizar — no adivine. ReportMemoryLeaksOnShutdown := True captura fugas al salir del programa (builds debug). Pitfalls comunes de rendimiento en Delphi: (1) Concatenación de cadenas en bucles crea copias — use TStringBuilder o pre-asigne. (2) SetLength en un bucle reasigna — establezca el tamaño una vez. (3) Pasar cadenas/arrays por valor las copia — use 'const' para parámetros de solo lectura. (4) TStringList.Sorted + Find es O(log n); IndexOf sin ordenar es O(n). (5) TList<T>.Add es amortizado O(1) pero Insert al frente es O(n). Para profiling profundo, use Sampling Profiler (gratis), AQTime o GpProfile — identifican hotspots sin cambios de código. Optimice el 20% del código que toma el 80% del tiempo.

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

Gestión de Memoria y Fugas

La gestión de memoria es la mayor fuente de bugs en Delphi. Regla #1: cada Create debe tener un Free coincidente. Use try/finally religiosamente. Para gestión automática, use interfaces (TInterfacedObject + conteo de referencias) — sin Free necesario. TObjectList<T> con OwnsObjects := True libera objetos contenidos automáticamente. ReportMemoryLeaksOnShutdown := True muestra un diálogo listando objetos con fugas al salir (solo debug). FastMM (el gestor de memoria default) en FullDebugMode registra fugas con stack traces de asignación a un archivo — esencial para rastrear fugas. Patrones comunes de fuga: try/finally faltante, event handlers no removidos, referencias circulares (corrija con [Weak]), hilos no liberados, objetos globales no liberados en finalization. La sección finalization de la unit se ejecuta al apagar — úsela para limpieza 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

Calidad de Código y Pruebas

DUnitX es el framework moderno de pruebas unitarias (reemplaza DUnit). [TestFixture] marca classes de prueba, [Test] marca métodos de prueba, [Setup]/[TearDown] se ejecutan antes/después de cada prueba. [TestCase] parametriza pruebas con datos inline. Assert.AreEqual/IsTrue/WillRaise verifican resultados. Desarrollo guiado por pruebas (TDD): escriba pruebas primero, luego código. Las pruebas capturan regresiones y documentan comportamiento esperado. Delphi Mocks (o mocking de Spring4D) crea objetos mock desde interfaces — Setup.Expect define expectativas, VerifyAll verifica que se cumplieron. Mocking es esencial para aislar units (mock base de datos, red, sistema de archivos). Apunte a alta cobertura de lógica de negocio. Ejecute pruebas en CI (integración continua) para capturar regresiones temprano. Las pruebas de integración verifican que los componentes funcionen juntos; las pruebas unitarias verifican units individuales de forma aislada.

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

Genéricos y Colecciones

Declaración de clase genérica

Los genéricos le permiten escribir contenedores con seguridad de tipos sin casts. Declare con <T> después del nombre del tipo. El compilador genera una versión especializada por tipo usado. Use TArray<T> en lugar de array of para arrays dinámicos en tipos genéricos.

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;

Uso de TDictionary

TDictionary<K,V> es el hash map genérico. Add lanza en claves duplicadas; OrAdd hace upsert. TryGetValue devuelve false (no excepción) en clave faltante. Siempre libere los diccionarios — no poseen objetos por defecto.

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 con comparer

TList<T>.Sort usa IComparer<T>. TComparer<T>.Construct envuelve una función anónima en un comparer. BinarySearch requiere que la lista esté ordenada con el mismo comparer. AddRange acepta un open array u otra lista.

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;

Constraints genéricos

Las constraints limitan qué tipos pueden sustituirse: 'class' (tipo de referencia), 'record' (tipo de valor), 'constructor' (constructor sin parámetros), o una clase ancestro específica. Múltiples constraints separadas por comas. Sin 'constructor' no puede llamar 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;

Ownership de objetos con TObjectDictionary

TObjectDictionary<K,V> extiende TDictionary con ownership. Pase [doOwnsValues], [doOwnsKeys], o ambos. En Remove/Clear/Free, los objetos owned se liberan automáticamente — previene fugas de memoria en colecciones de objetos.

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étodos Anónimos y Closures

Método anónimo básico

Los métodos anónimos son referencias a funciones inline. TFunc<...> es para functions, TProc<...> para procedures. Capturan variables del scope envolvente (closures). Asignables a variables, pasables como parámetros.

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;

Closure capturando variables

Las variables capturadas se asignan en el heap y viven tanto como el método anónimo. Cada llamada a MakeMultiplier captura su propio Factor — los closures son independientes. Así es como funcionan factories y partial application.

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;

Funciones de orden superior

'reference to' declara un tipo procedural compatible con métodos anónimos. Apply es una función de orden superior — toma una función como argumento. Esto habilita patrones map/filter/reduce. Use TArray<Integer> para arrays dinámicos.

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;

Event handlers con closures

Los métodos anónimos pueden reemplazar event handlers tradicionales basados en métodos, capturando contexto sin campos. Útil para handlers únicos y reducir boilerplate. El Caption capturado se mantiene vivo con la referencia closure mantenida por 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 con anónimo

CreateAnonymousThread envuelve un closure en un hilo — trabajo background fire-and-forget. Use TThread.Queue (o Synchronize) para hacer marshal de actualizaciones de UI de vuelta al hilo principal. Nunca toque controles de UI directamente desde un hilo worker.

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

Atributos y RTTI

Declaración de atributo personalizado

Los atributos son classes que heredan TCustomAttribute. Aplique con [AttrName(...)] en tipos, campos, métodos, propiedades. El compilador los embebe en RTTI. Los parámetros del constructor se vuelven argumentos del atributo.

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;

Lectura de atributos vía RTTI

TRttiContext es el punto de entrada a RTTI. GetType devuelve TRttiType para una clase. GetAttributes devuelve todos los atributos aplicados. Haga cast a su tipo de atributo para leer propiedades. RTTI requiere que la clase esté en una unit compilada con {$M+} o derivada 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 de campos y métodos

GetFields devuelve todos los campos public/published. SetValue/GetValue proporcionan acceso dinámico a campos por nombre — útil para serializadores y ORMs. GetMethods devuelve todos los métodos incluyendo heredados. RTTI es más lento que llamadas directas.

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 de propiedades e invocación

GetProperties devuelve propiedades published. IsReadable/IsWritable verifican accessors. GetValue/SetValue funcionan en propiedades también. TypeKind (tkInteger, tkString, tkClass, etc.) le permite manejar cada tipo apropiadamente. Así es como funcionan la mayoría de serializadores de 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;

Invocación de métodos por nombre

GetMethod encuentra un método por nombre (sensible a mayúsculas). Invoke lo llama dinámicamente con argumentos array de TValue. TValue es un wrapper tipo-variant para cualquier tipo. Útil para sistemas de plugins, scripting y late binding. Devuelve TValue — convierta con 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 en Profundidad

Declaración e implementación de interfaz

Las interfaces definen contratos sin implementación. Los GUIDs (opcionales pero recomendados) habilitan casts 'as' y Supports(). TInterfacedObject proporciona conteo de referencias. Todos los métodos de interfaz deben implementarse (sin escape 'abstract'). Las propiedades en interfaces necesitan métodos accessor.

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;

Conteo de referencias y memoria

Las referencias de interfaz son reference-counted. Cuando la última referencia de interfaz sale del scope, el objeto se libera. NUNCA mezcle referencias de objeto e interfaz a la misma instancia — el refcounting de interfaz lo liberará mientras el puntero de objeto aún apunta a él. Elija un modelo de ownership.

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;

Herencia de interfaz y múltiples interfaces

Las interfaces pueden heredar de múltiples parents. Una clase puede implementar múltiples interfaces. Las cláusulas de resolución de métodos (method = interface.method) resuelven conflictos cuando múltiples interfaces declaran el mismo método. Use 'as' o Supports() para consultar una interfaz en runtime.

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 y casts as

Supports() verifica si un objeto implementa una interfaz — devuelve boolean, opcionalmente devuelve la interfaz. El cast 'as' hace lo mismo pero lanza EInvalidCast en fallo. Supports() funciona tanto en objetos como en referencias de interfaz. Requiere que la interfaz tenga 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;

Patrón de inyección de dependencias

Pase dependencias como interfaces — habilita mocking, intercambio de implementaciones y testabilidad. La clase depende de la abstracción (ILogger), no de un tipo concreto. Esta es la base de contenedores DI como Spring4D. El ownership de interfaz significa que el logger vive tanto como el servicio mantenga la referencia.

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

Gestión Avanzada de Memoria

Patrón try-finally

Siempre empareje asignación con Free en try-finally. Anide bloques finally para múltiples recursos. FreeAndNil (en lugar de Free) también limpia la variable — útil para detectar use-after-free. Free es seguro en nil — no necesita verificar Assigned primero.

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;

Ownership basado en interfaces

TInterfacedObject + referencia de interfaz = limpieza automática. Cuando la interfaz sale del scope, el destructor se ejecuta. Esto es RAII en Delphi — envuelva recursos en objetos interfaced para limpieza garantizada sin 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)

Referencias débiles

Las referencias weak rompen ciclos de referencias. Sin [Weak], dos objetos manteniendo referencias de interfaz entre sí nunca se liberarían (ciclo). TComponent tiene un mecanismo FreeNotification integrado para referencias weak. El atributo [Weak] requiere RTTI y funciona en campos de interfaz y clase.

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;

Records vs objetos

Los records son tipos de valor (stack, copiados en asignación) — sin gestión de memoria necesaria. Las classes son tipos de referencia (heap, deben liberarse). Use records para datos pequeños inmutables (puntos, fechas, dinero). Use classes para objetos polimórficos o grandes. Los records pueden tener métodos y operadores en Delphi moderno.

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;

Detección de fugas de memoria

ReportMemoryLeaksOnShutdown muestra un diálogo al salir listando objetos con fugas. FastMM (el gestor de memoria default) detecta fugas, double-frees y use-after-free. Para producción, registre fugas a archivo. Ejecute verificaciones de fugas regularmente durante el desarrollo — más fácil corregir fugas a medida que se introducen.

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)

Fundamentos de form cross-platform

Los forms FMX son cross-platform (Windows, macOS, iOS, Android, Linux). Mismo código, diferentes renderers nativos. Use units FMX.* en lugar de Vcl.* Los controles son vector-based (escalan perfectamente). Los styles reemplazan themes — la apariencia visual es data-driven.

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.

Layouts y alineación

FMX usa Align (Client, Top, Bottom, Left, Right, None) y Margins/Padding para layout. TFlowLayout organiza hijos como CSS flexbox. TGridLayout hace una grid. Use TScaleBox para escalado independiente de resolución. Los layouts son controles en sí mismos — anidables.

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 y styling

Los styles son colecciones de recursos visuales (brushes, fonts, effects) almacenados en archivos .fsf o .style. StyleLookup elige un style nombrado para un control. TStyleManager cambia styles globales en runtime. Los styles FMX son vector — escalan a cualquier DPI. El Style Designer edita styles visualmente.

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

Effects y animaciones

Los effects (Glow, Shadow, Blur, Reflection) son componentes no-visuales parented a un control. Las animaciones (TFloatAnimation, TColorAnimation, TPathAnimation) animan propiedades a lo largo del tiempo. Establezca Parent al control objetivo. Trigger/Start para comenzar. Todo GPU-accelerated — fluido en todas las plataformas.

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;

Servicios de plataforma

Los platform services abstraen características específicas del OS. Consulte con SupportsPlatformService — devuelve false en plataformas no soportadas. Siempre verifique antes de usar. Servicios comunes: clipboard, diálogos, teclado virtual, device info, screen. Este patrón mantiene su código cross-platform sin bloques {$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 Datos (FireDAC)

Configuración de conexión

TFDConnection es el objeto central de FireDAC. Establezca DriverName (SQLite, MSSQL, MySQL, PostgreSQL, Oracle, etc.) y Params. Las definiciones de conexión pueden almacenarse en un archivo .ini para reutilización. Siempre establezca Connected := False antes de liberar. Use un TFDManager para pool de conexiones.

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;

Ejecución de queries

Use Open para SELECT (devuelve un cursor), execSQL para INSERT/UPDATE/DELETE (devuelve filas afectadas). SIEMPRE use parámetros — nunca concatene valores en SQL (riesgo de inyección). ParamByName es insensible a mayúsculas. FieldByName accede columnas por nombre. Eof/Next iteran filas.

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;

Transacciones

StartTransaction/Commit/Rollback envuelven operaciones atómicas. Si alguna sentencia falla, Rollback deshace todos los cambios. Las transacciones anidadas usan savepoints (rollback parcial). Siempre envuelva en try-except-raise para propagar el error después del rollback. Sin transacción, cada sentencia auto-confirma.

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 y live data

TFDTable es un cursor live y editable sobre una tabla. Edit/Post modifica la fila actual. Append/Post inserta. Delete remueve la fila actual. Los cambios van directamente a la base de datos. Use IndexFieldNames para ordenamiento. Para consultas complejas, use TFDQuery en su lugar.

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;

Actualizaciones por lote y modo cached

El modo CachedUpdates bufferiza cambios en memoria — aplíquelos todos a la vez con ApplyUpdates. Más rápido que actualizaciones por fila para operaciones masivas. CancelUpdates descarta el buffer. Status muestra el tipo de cambio por fila. Útil para escenarios desconectados y reducir round-trips.

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 y HTTP

Fundamentos de TRESTClient

TRESTClient mantiene la URL base. TRESTRequest construye la petición (método, recurso, parámetros). TRESTResponse mantiene el resultado. Los segmentos de URL ({id}) se sustituyen por AddUrlSegment. StatusCode/Content dan la respuesta HTTP. Libere en orden inverso de creación.

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;

Parseo JSON

System.JSON proporciona TJSONObject, TJSONArray, TJSONValue. ParseJSONValue parsea una cadena (devuelve TJSONValue — haga cast según sea necesario). GetValue<T> lee valores tipados. AddPair/AddElement construyen JSON. Todos los objetos JSON deben liberarse — son reference-counted solo cuando son owned por 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;

Servidor REST con datasnap

DataSnap expone métodos de Delphi como endpoints REST automáticamente. Los nombres de métodos se vuelven segmentos de URL. Los parámetros mapean a segmentos de URL o body POST. TJSONObject/TJSONArray son los tipos de retorno estándar. Aplique atributos como [httppost] para especificar verbos HTTP. Use TDSServerModule como clase 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 para control de bajo nivel

TIdHTTP (Indy) da control completo sobre HTTP — headers, cookies, redirects, timeouts. Más verboso que TRESTClient pero más flexible. Para HTTPS, asigne un SSL IOHandler (TIdSSLIOHandlerSocketOpenSSL). Establezca ReadTimeout/ConnectTimeout para producción. Indy es sincrónico — envuelva en TThread para 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 asíncrono con tasks

Envuelva llamadas REST en TTask.Run para evitar bloquear el hilo de UI. Haga marshal de actualizaciones de UI con TThread.Queue (async) o TThread.Synchronize (sync). Tenga cuidado con los lifetimes de objetos — la petición debe sobrevivir a la task. Considere TRESTRequest.ExecuteAsync para soporte async integrado.

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

Multihilo (Paralelo)

Fundamentos de TThread

Subclase TThread y sobrescriba Execute. Create(False) inicia inmediatamente; Create(True) requiere .Start. FreeOnTerminate := True auto-libera — nunca llame Free en tales hilos. Verifique Terminated periódicamente para shutdown graceful. Nunca toque UI desde Execute — use 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 y futures

ITask/IFuture<T> de System.Threading son de más alto nivel que TThread. Los futures devuelven un valor tipado — .Value bloquea hasta que el resultado está listo. Las tasks son reference-counted (sin Free manual). Use TTask.WaitForAll / WaitForAny para coordinar múltiples tasks. Más fáciles de usar que TThread crudo.

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;

Bucle for paralelo

TParallel.For ejecuta iteraciones de bucle en paralelo a través de cores de CPU. DEBE sincronizar estado compartido (use TCriticalSection o TInterlocked). El orden de iteración es non-deterministic. Use TLoopState para break/continue. Más rápido para trabajo CPU-bound; más lento para iteraciones triviales debido al overhead.

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;

Primitivas de sincronización

TCriticalSection: exclusión mutua (solo un hilo a la vez). TEvent: señal entre hilos (SetEvent/WaitFor). TEvent con manual reset permanece señalado hasta Reset. TInterlocked.Increment es atómico y más rápido que una critical section para contadores simples. TMonitor (integrado en TObject) es otra opción.

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 y Synchronize

Los controles de UI solo pueden tocarse desde el hilo principal. Synchronize bloquea al worker hasta que el hilo principal ejecuta el método anónimo — use con moderación (causa serialización). Queue posta y continúa — preferido para actualizaciones de UI fire-and-forget. Pase nil como argumento thread para usar el hilo actual.

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 y Componentes

Fundamentos de proyecto Package

Los packages (.bpl) son DLLs con metadatos de Delphi — comparten código entre apps. 'requires' lista dependencias. 'contains' lista units en este package. Los design-time packages instalan componentes en el IDE; los runtime packages se distribuyen con la app. Separe design/runtime para mantener bajo el bloat del 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)

Esqueleto de componente personalizado

Derive de la class existente más cercana (TCustomLabel da un label sin propiedades published). Re-publish solo las propiedades que quiere exponer. El procedimiento Register añade el componente a la paleta del IDE. 'default' establece el valor inicial (debe coincidir con el constructor). Coloque Register en un design-time package.

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;

Propiedades de componente y editores

TComponent es la base para componentes no-visuales. Posea sub-objetos (FItems) — cree en constructor, libere en destructor. Las propiedades TStrings obtienen un editor de cadenas integrado. RegisterPropertyEditor personaliza el Object Inspector para propiedades específicas. Use TPersistent para objetos anidados que necesitan 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);

Eventos y punteros a método

Los tipos de evento son tipos procedurales con 'of object' — mantienen tanto una referencia de objeto como un puntero a método. Siempre verifique Assigned() antes de llamar — eventos nil lanzan AVs. Los métodos Do* (DoChange, DoClick) son los dispatchers protegidos que disparan eventos. Las subclases pueden sobrescribir Do* para interceptar eventos.

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 y persistencia

TPersistent habilita streaming y Assign. Las propiedades published se guardan automáticamente a archivos DFM. Sobrescriba Assign para soportar copia entre objetos. DefineProperties añade datos no-published al stream. WriteComponent/ReadComponent serializan a cualquier TStream. Así es como los forms persisten su estado.

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?