Cómo Generar UUID en Delphi: Guía Completa con Ejemplos

Comprendiendo los UUIDs en el Desarrollo con Delphi

Los Identificadores Únicos Universales (UUIDs), también conocidos como GUIDs (Identificadores Únicos Globales) en el desarrollo de Windows, son valores de 128 bits que proporcionan identificación única a través de sistemas distribuidos. En la programación con Delphi, los GUIDs son esenciales para interfaces COM, claves primarias de bases de datos, aplicaciones distribuidas y para garantizar la unicidad sin coordinación centralizada. Ya sea que estés desarrollando aplicaciones de escritorio con VCL/FMX o aplicaciones de servidor, comprender la generación de UUID es crucial.

Esta guía completa explora múltiples enfoques para generar UUIDs en aplicaciones Delphi, cubriendo tanto las funciones nativas RTL como las llamadas a la API de Windows. Examinaremos estrategias de implementación para diferentes versiones de Delphi, desde el clásico Delphi 7 hasta las últimas versiones de RAD Studio, así como consideraciones multiplataforma para aplicaciones FireMonkey. Las técnicas presentadas aquí también se aplican al desarrollo con Free Pascal y Lazarus.

4 Métodos Efectivos para Generar UUIDs en Delphi

Método 1: Usando la Función CreateGUID

La forma más simple y común de generar un UUID en Delphi es usando la función incorporada CreateGUID de la unidad System.SysUtils. Este método funciona en todas las versiones y plataformas de Delphi.

program GenerarUUID;

{$APPTYPE CONSOLE}

uses
  System.SysUtils;

var
  MiGUID: TGUID;
  CadenaGUID: string;
begin
  try
    // Generar un nuevo GUID
    if CreateGUID(MiGUID) = S_OK then
    begin
      // Convertir GUID a representación de cadena
      CadenaGUID := GUIDToString(MiGUID);
      
      WriteLn('UUID Generado: ', CadenaGUID);
      // Ejemplo de salida: UUID Generado: {8C2D56F0-8A1E-4B6C-A8D2-AF3A99B11F75}
      
      // Alternativa: Obtener cadena sin llaves
      CadenaGUID := MiGUID.ToString;
      WriteLn('Sin llaves: ', CadenaGUID);
      // Ejemplo de salida: Sin llaves: 8C2D56F0-8A1E-4B6C-A8D2-AF3A99B11F75
    end
    else
      WriteLn('Error al crear GUID');
      
    ReadLn;
  except
    on E: Exception do
      WriteLn('Error: ', E.Message);
  end;
end.

CreateGUID genera un UUID Versión 4 (aleatorio) usando el generador de números aleatorios criptográficamente seguro del sistema operativo. En Windows, llama internamente a CoCreateGuid, mientras que en otras plataformas usa las funciones apropiadas del sistema. La función devuelve S_OK (0) en caso de éxito. El tipo de registro TGUID representa la estructura GUID, y GUIDToString la convierte al formato de cadena estándar con llaves.

Método 2: Llamada Directa a la API de Windows

Para aplicaciones específicas de Windows o cuando necesitas más control, puedes llamar directamente a la función CoCreateGuid de la API de Windows. Este enfoque requiere la unidad Winapi.ActiveX.

program GUIDAPIWindows;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  Winapi.Windows,
  Winapi.ActiveX;

function GenerarGUIDWindows: string;
var
  NuevoGUID: TGUID;
  hr: HResult;
begin
  // Inicializar COM (requerido para CoCreateGuid)
  CoInitialize(nil);
  try
    // Llamar directamente a la API de Windows
    hr := CoCreateGuid(NuevoGUID);
    
    if Succeeded(hr) then
    begin
      // Convertir a formato de cadena
      Result := GUIDToString(NuevoGUID);
    end
    else
    begin
      raise Exception.CreateFmt('CoCreateGuid falló con error: %d', [hr]);
    end;
  finally
    // Limpiar COM
    CoUninitialize;
  end;
end;

begin
  try
    WriteLn('GUID API Windows: ', GenerarGUIDWindows);
    
    // Generar múltiples GUIDs
    WriteLn('Generando 5 GUIDs:');
    for var i := 1 to 5 do
      WriteLn(Format('  GUID %d: %s', [i, GenerarGUIDWindows]));
      
    ReadLn;
  except
    on E: Exception do
      WriteLn('Error: ', E.Message);
  end;
end.

Este método interactúa directamente con el subsistema COM de Windows para generar GUIDs. CoCreateGuid es la API de Windows subyacente que CreateGUID llama en plataformas Windows. Aunque este enfoque requiere inicialización COM, proporciona acceso directo a la generación de GUID de Windows y puede ser útil cuando trabajas con interfaces COM o cuando necesitas asegurar que estás usando la implementación específica de Windows.

Método 3: GUIDs con Formato Personalizado

Delphi permite formateo flexible de GUIDs para cumplir con varios requisitos, como eliminar llaves, usar diferentes separadores o crear representaciones personalizadas para sistemas específicos.

program GUIDsFormateados;

{$APPTYPE CONSOLE}

uses
  System.SysUtils;

type
  TGUIDHelper = record helper for TGUID
    function ACadenaCompacta: string;
    function ACadenaMayusculas: string;
    function AFormatoRegistro: string;
    function AFormatoURN: string;
  end;

function TGUIDHelper.ACadenaCompacta: string;
begin
  // Eliminar todo el formato (32 dígitos hex)
  Result := Format('%.8x%.4x%.4x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x',
    [D1, D2, D3, D4[0], D4[1], D4[2], D4[3], D4[4], D4[5], D4[6], D4[7]]);
end;

function TGUIDHelper.ACadenaMayusculas: string;
begin
  Result := UpperCase(ToString);
end;

function TGUIDHelper.AFormatoRegistro: string;
begin
  // El formato de registro usa llaves
  Result := GUIDToString(Self);
end;

function TGUIDHelper.AFormatoURN: string;
begin
  // Formato URN: urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
  Result := 'urn:uuid:' + ToString;
end;

var
  MiGUID: TGUID;
begin
  CreateGUID(MiGUID);
  
  WriteLn('Formato estándar:    ', MiGUID.ToString);
  WriteLn('Con llaves:          ', GUIDToString(MiGUID));
  WriteLn('Compacto (sin guiones):', MiGUID.ACadenaCompacta);
  WriteLn('Mayúsculas:          ', MiGUID.ACadenaMayusculas);
  WriteLn('Formato registro:    ', MiGUID.AFormatoRegistro);
  WriteLn('Formato URN:         ', MiGUID.AFormatoURN);
  
  // Formato personalizado con separadores específicos
  WriteLn('Formato personalizado:',
    Format('%.8x_%.4x_%.4x_%.2x%.2x_%.2x%.2x%.2x%.2x%.2x%.2x',
    [MiGUID.D1, MiGUID.D2, MiGUID.D3, MiGUID.D4[0], MiGUID.D4[1],
     MiGUID.D4[2], MiGUID.D4[3], MiGUID.D4[4], MiGUID.D4[5],
     MiGUID.D4[6], MiGUID.D4[7]]));
  
  ReadLn;
end.

Este ejemplo demuestra varias opciones de formateo de GUID usando un helper de registro. El registro TGUID contiene campos D1 (longword), D2 y D3 (word), y D4 (array de 8 bytes) que representan la estructura GUID. Puedes acceder a estos campos directamente para formateo personalizado. El formato compacto es útil para URLs o cuando el espacio de almacenamiento es limitado, mientras que el formato URN se usa en varios estándares de Internet.

Método 4: Generación Determinista de UUID

Aunque los GUIDs aleatorios son adecuados para la mayoría de los casos, a veces necesitas UUIDs deterministas que se puedan regenerar a partir de entradas específicas. Esta implementación crea UUIDs Versión 5 usando hash SHA-1.

program GUIDDeterminista;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  System.Hash;

type
  // GUIDs de espacio de nombres estándar de RFC 4122
  TEspacioNombresGUID = record
    const DNS: TGUID = '{6ba7b810-9dad-11d1-80b4-00c04fd430c8}';
    const URL: TGUID = '{6ba7b811-9dad-11d1-80b4-00c04fd430c8}';
    const OID: TGUID = '{6ba7b812-9dad-11d1-80b4-00c04fd430c8}';
    const X500: TGUID = '{6ba7b814-9dad-11d1-80b4-00c04fd430c8}';
  end;

function CrearGUIDBasadoEnNombre(const EspacioNombresGUID: TGUID; const Nombre: string): TGUID;
var
  Hash: THashSHA1;
  BytesHash: TBytes;
  BytesEspacioNombres: TBytes;
  BytesNombre: TBytes;
  BytesCombinados: TBytes;
  i: Integer;
begin
  // Convertir GUID de espacio de nombres a bytes
  SetLength(BytesEspacioNombres, 16);
  Move(EspacioNombresGUID.D1, BytesEspacioNombres[0], 4);
  Move(EspacioNombresGUID.D2, BytesEspacioNombres[4], 2);
  Move(EspacioNombresGUID.D3, BytesEspacioNombres[6], 2);
  Move(EspacioNombresGUID.D4[0], BytesEspacioNombres[8], 8);
  
  // Intercambiar orden de bytes para orden de bytes de red (big-endian)
  // D1 es 4 bytes
  for i := 0 to 1 do
  begin
    var temp := BytesEspacioNombres[i];
    BytesEspacioNombres[i] := BytesEspacioNombres[3-i];
    BytesEspacioNombres[3-i] := temp;
  end;
  // D2 es 2 bytes
  var temp := BytesEspacioNombres[4];
  BytesEspacioNombres[4] := BytesEspacioNombres[5];
  BytesEspacioNombres[5] := temp;
  // D3 es 2 bytes  
  temp := BytesEspacioNombres[6];
  BytesEspacioNombres[6] := BytesEspacioNombres[7];
  BytesEspacioNombres[7] := temp;
  
  // Convertir nombre a bytes UTF-8
  BytesNombre := TEncoding.UTF8.GetBytes(Nombre);
  
  // Combinar espacio de nombres y nombre
  SetLength(BytesCombinados, Length(BytesEspacioNombres) + Length(BytesNombre));
  Move(BytesEspacioNombres[0], BytesCombinados[0], Length(BytesEspacioNombres));
  Move(BytesNombre[0], BytesCombinados[Length(BytesEspacioNombres)], Length(BytesNombre));
  
  // Calcular hash SHA-1
  Hash := THashSHA1.Create;
  Hash.Update(BytesCombinados);
  BytesHash := Hash.HashAsBytes;
  
  // Copiar primeros 16 bytes a GUID
  Move(BytesHash[0], Result.D1, 4);
  Move(BytesHash[4], Result.D2, 2);
  Move(BytesHash[6], Result.D3, 2);
  Move(BytesHash[8], Result.D4[0], 8);
  
  // Establecer bits de versión (5) y variante
  Result.D3 := (Result.D3 and $0FFF) or $5000; // Versión 5
  Result.D4[0] := (Result.D4[0] and $3F) or $80; // Variante 10
end;

begin
  try
    var NombrePrueba := 'ejemplo.com/usuario/12345';
    
    // Generar GUIDs deterministas
    var GUID1 := CrearGUIDBasadoEnNombre(TEspacioNombresGUID.URL, NombrePrueba);
    WriteLn('GUID basado en URL para "', NombrePrueba, '":');
    WriteLn('  ', GUIDToString(GUID1));
    
    // La misma entrada produce la misma salida
    var GUID2 := CrearGUIDBasadoEnNombre(TEspacioNombresGUID.URL, NombrePrueba);
    WriteLn('  Misma entrada: ', GUIDToString(GUID2));
    WriteLn('  Igual: ', GUIDToString(GUID1) = GUIDToString(GUID2));
    
    WriteLn;
    
    // Diferentes espacios de nombres producen diferentes GUIDs
    var GUID3 := CrearGUIDBasadoEnNombre(TEspacioNombresGUID.DNS, 'ejemplo.com');
    WriteLn('GUID basado en DNS para "ejemplo.com":');
    WriteLn('  ', GUIDToString(GUID3));
    
    ReadLn;
  except
    on E: Exception do
      WriteLn('Error: ', E.Message);
  end;
end.

Esta implementación crea UUIDs RFC 4122 Versión 5 usando hash SHA-1. La misma combinación de espacio de nombres y nombre siempre produce el mismo UUID, lo que lo hace ideal para sistemas distribuidos donde diferentes nodos necesitan generar identificadores idénticos para el mismo recurso. El espacio de nombres proporciona contexto (DNS, URL, etc.) mientras que el nombre es el identificador específico. Ten en cuenta que la conversión del orden de bytes es necesaria porque el RFC especifica orden de bytes de red.

Creando una Unidad de Utilidad UUID Reutilizable

Para aplicaciones que usan frecuentemente UUIDs, es beneficioso crear una unidad de utilidad que centralice la lógica de generación de UUID. Aquí hay una unidad de utilidad completa para aplicaciones Delphi:

unit UtilidadesUUID;

interface

uses
  System.SysUtils, System.Hash;

type
  /// <summary>
  /// Clase de utilidad para generación y manipulación de UUID/GUID
  /// </summary>
  TGeneradorUUID = class
  public
    type
      TVersionUUID = (uvAleatoria, uvBasadaEnTiempo, uvBasadaEnNombre);
      
    class var
      // Espacios de nombres estándar de RFC 4122
      EspacioNombresDNS: TGUID;
      EspacioNombresURL: TGUID;
      EspacioNombresOID: TGUID;
      EspacioNombresX500: TGUID;
      
  private
    class constructor Create;
    class function IntercambiarEndian(const AGUID: TGUID): TGUID;
    
  public
    /// <summary>Generar un UUID aleatorio (Versión 4)</summary>
    class function NuevoGUID: TGUID;
    
    /// <summary>Generar un UUID secuencial para optimización de base de datos</summary>
    class function NuevoGUIDSecuencial: TGUID;
    
    /// <summary>Generar un UUID basado en nombre (Versión 5)</summary>
    class function NuevoGUIDBasadoEnNombre(const EspacioNombresGUID: TGUID; 
      const Nombre: string): TGUID;
      
    /// <summary>Generar UUID determinista basado en URL</summary>
    class function NuevoGUIDBasadoEnURL(const URL: string): TGUID;
    
    /// <summary>Generar UUID determinista basado en DNS</summary>
    class function NuevoGUIDBasadoEnDNS(const DNS: string): TGUID;
    
    /// <summary>Parsear cadena a GUID con validación</summary>
    class function IntentarParsearGUID(const S: string; out GUID: TGUID): Boolean;
    
    /// <summary>Verificar si GUID está vacío/nil</summary>
    class function EsGUIDVacio(const GUID: TGUID): Boolean;
    
    /// <summary>Obtener GUID sin llaves ni guiones</summary>
    class function ObtenerGUIDCompacto(const GUID: TGUID): string;
  end;

implementation

uses
  System.DateUtils, Winapi.Windows;

{ TGeneradorUUID }

class constructor TGeneradorUUID.Create;
begin
  // Inicializar espacios de nombres estándar
  EspacioNombresDNS := StringToGUID('{6ba7b810-9dad-11d1-80b4-00c04fd430c8}');
  EspacioNombresURL := StringToGUID('{6ba7b811-9dad-11d1-80b4-00c04fd430c8}');
  EspacioNombresOID := StringToGUID('{6ba7b812-9dad-11d1-80b4-00c04fd430c8}');
  EspacioNombresX500 := StringToGUID('{6ba7b814-9dad-11d1-80b4-00c04fd430c8}');
end;

class function TGeneradorUUID.NuevoGUID: TGUID;
begin
  if CreateGUID(Result) <> S_OK then
    raise Exception.Create('Error al crear GUID');
end;

class function TGeneradorUUID.NuevoGUIDSecuencial: TGUID;
var
  MarcaTiempo: Int64;
  Contador: Word;
begin
  // Generar GUID base
  Result := NuevoGUID;
  
  // Obtener marca de tiempo actual
  MarcaTiempo := DateTimeToUnix(Now, False) * 10000000;
  
  // Incrustar marca de tiempo en primeros 6 bytes para orden secuencial
  Result.D1 := LongWord(MarcaTiempo shr 16);
  Result.D2 := Word(MarcaTiempo);
  
  // Mantener bits de versión 4 y variante
  Result.D3 := (Result.D3 and $0FFF) or $4000;
  Result.D4[0] := (Result.D4[0] and $3F) or $80;
end;

class function TGeneradorUUID.IntercambiarEndian(const AGUID: TGUID): TGUID;
begin
  Result := AGUID;
  // Intercambiar D1 (4 bytes)
  Result.D1 := ((AGUID.D1 and $000000FF) shl 24) or
               ((AGUID.D1 and $0000FF00) shl 8) or
               ((AGUID.D1 and $00FF0000) shr 8) or
               ((AGUID.D1 and $FF000000) shr 24);
  // Intercambiar D2 (2 bytes)
  Result.D2 := ((AGUID.D2 and $00FF) shl 8) or
               ((AGUID.D2 and $FF00) shr 8);
  // Intercambiar D3 (2 bytes)
  Result.D3 := ((AGUID.D3 and $00FF) shl 8) or
               ((AGUID.D3 and $FF00) shr 8);
end;

class function TGeneradorUUID.NuevoGUIDBasadoEnNombre(const EspacioNombresGUID: TGUID;
  const Nombre: string): TGUID;
var
  Hash: THashSHA1;
  BytesHash: TBytes;
  BytesEspacioNombres: TBytes;
  GUIDIntercambiado: TGUID;
begin
  // Convertir a orden de bytes de red
  GUIDIntercambiado := IntercambiarEndian(EspacioNombresGUID);
  
  // Preparar datos para hash
  SetLength(BytesEspacioNombres, 16);
  Move(GUIDIntercambiado, BytesEspacioNombres[0], 16);
  
  // Calcular hash
  Hash := THashSHA1.Create;
  Hash.Update(BytesEspacioNombres);
  Hash.Update(TEncoding.UTF8.GetBytes(Nombre));
  BytesHash := Hash.HashAsBytes;
  
  // Crear GUID desde hash
  Move(BytesHash[0], Result, 16);
  
  // Convertir de vuelta desde orden de bytes de red
  Result := IntercambiarEndian(Result);
  
  // Establecer versión 5 y variante
  Result.D3 := (Result.D3 and $0FFF) or $5000;
  Result.D4[0] := (Result.D4[0] and $3F) or $80;
end;

class function TGeneradorUUID.NuevoGUIDBasadoEnURL(const URL: string): TGUID;
begin
  Result := NuevoGUIDBasadoEnNombre(EspacioNombresURL, URL);
end;

class function TGeneradorUUID.NuevoGUIDBasadoEnDNS(const DNS: string): TGUID;
begin
  Result := NuevoGUIDBasadoEnNombre(EspacioNombresDNS, DNS);
end;

class function TGeneradorUUID.IntentarParsearGUID(const S: string; 
  out GUID: TGUID): Boolean;
begin
  try
    GUID := StringToGUID(S);
    Result := True;
  except
    Result := False;
  end;
end;

class function TGeneradorUUID.EsGUIDVacio(const GUID: TGUID): Boolean;
const
  GUIDVacio: TGUID = '{00000000-0000-0000-0000-000000000000}';
begin
  Result := CompareMem(@GUID, @GUIDVacio, SizeOf(TGUID));
end;

class function TGeneradorUUID.ObtenerGUIDCompacto(const GUID: TGUID): string;
begin
  Result := Format('%.8x%.4x%.4x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x',
    [GUID.D1, GUID.D2, GUID.D3, GUID.D4[0], GUID.D4[1], GUID.D4[2], 
     GUID.D4[3], GUID.D4[4], GUID.D4[5], GUID.D4[6], GUID.D4[7]]);
end;

end.

Esta unidad de utilidad proporciona un conjunto completo de métodos de generación de UUID encapsulados en una clase con métodos de clase. Incluye generación de UUID aleatoria, UUIDs secuenciales para optimización de base de datos y UUIDs deterministas basadas en nombre siguiendo RFC 4122. La unidad maneja la conversión de endianness para la generación correcta de UUID Versión 5 y proporciona métodos auxiliares para análisis y formateo. Puedes extender fácilmente esta unidad con funcionalidad adicional como generación de UUID Versión 1 (basada en tiempo) o Versión 3 (basada en MD5).

Análisis y Validación de GUIDs en Delphi

Al trabajar con GUIDs de fuentes externas o entrada de usuario, la validación adecuada es crucial. Aquí está cómo analizar y validar GUIDs en aplicaciones Delphi:

program ValidarGUIDs;

{$APPTYPE CONSOLE}

uses
  System.SysUtils, System.RegularExpressions;

type
  TValidadorGUID = class
  private
    const
      // Patrón GUID compatible con RFC 4122
      PatronGUID = '^[{(]?[0-9A-Fa-f]{8}[-]?([0-9A-Fa-f]{4}[-]?){3}[0-9A-Fa-f]{12}[)}]?$';
      
  public
    class function EsGUIDValido(const Valor: string): Boolean;
    class function IntentarParsearGUID(const Valor: string; out GUID: TGUID): Boolean;
    class function NormalizarGUID(const Valor: string): string;
    class function ExtraerGUIDDeTexto(const Texto: string): TArray<string>;
  end;

class function TValidadorGUID.EsGUIDValido(const Valor: string): Boolean;
var
  RegEx: TRegEx;
begin
  RegEx := TRegEx.Create(PatronGUID);
  Result := RegEx.IsMatch(Valor);
end;

class function TValidadorGUID.IntentarParsearGUID(const Valor: string; 
  out GUID: TGUID): Boolean;
var
  ValorNormalizado: string;
begin
  Result := False;
  
  // Primero verificar con regex
  if not EsGUIDValido(Valor) then
    Exit;
    
  try
    // Normalizar la cadena GUID
    ValorNormalizado := NormalizarGUID(Valor);
    
    // Intentar convertir
    GUID := StringToGUID(ValorNormalizado);
    Result := True;
  except
    // Formato inválido
    Result := False;
  end;
end;

class function TValidadorGUID.NormalizarGUID(const Valor: string): string;
var
  ValorLimpio: string;
begin
  // Eliminar todos los caracteres no hexadecimales
  ValorLimpio := TRegEx.Replace(Valor, '[^0-9A-Fa-f]', '');
  
  // Verificar si tenemos exactamente 32 dígitos hexadecimales
  if Length(ValorLimpio) <> 32 then
    raise Exception.Create('Longitud de GUID inválida');
    
  // Formatear como GUID estándar
  Result := Format('{%s-%s-%s-%s-%s}',
    [Copy(ValorLimpio, 1, 8),
     Copy(ValorLimpio, 9, 4),
     Copy(ValorLimpio, 13, 4),
     Copy(ValorLimpio, 17, 4),
     Copy(ValorLimpio, 21, 12)]);
end;

class function TValidadorGUID.ExtraerGUIDDeTexto(const Texto: string): TArray<string>;
var
  RegEx: TRegEx;
  Coincidencias: TMatchCollection;
  Coincidencia: TMatch;
  i: Integer;
begin
  RegEx := TRegEx.Create(PatronGUID);
  Coincidencias := RegEx.Matches(Texto);
  
  SetLength(Result, Coincidencias.Count);
  for i := 0 to Coincidencias.Count - 1 do
    Result[i] := Coincidencias[i].Value;
end;

// Probar el validador
var
  CadenasPrueba: array[0..5] of string = (
    '{8C2D56F0-8A1E-4B6C-A8D2-AF3A99B11F75}',  // Válido con llaves
    '8C2D56F0-8A1E-4B6C-A8D2-AF3A99B11F75',    // Válido sin llaves
    '8C2D56F08A1E4B6CA8D2AF3A99B11F75',        // Válido sin formato
    '(8C2D56F0-8A1E-4B6C-A8D2-AF3A99B11F75)',  // Válido con paréntesis
    'no-es-guid',                               // Inválido
    '8C2D56F0-8A1E-4B6C-A8D2-AF3A99B11F7'      // Inválido (muy corto)
  );
  GUIDPrueba: TGUID;
  CadenaPrueba: string;
begin
  try
    WriteLn('Pruebas de Validación de GUID:');
    WriteLn('='*50);
    
    for CadenaPrueba in CadenasPrueba do
    begin
      Write(Format('%-40s: ', [CadenaPrueba]));
      
      if TValidadorGUID.IntentarParsearGUID(CadenaPrueba, GUIDPrueba) then
        WriteLn('Válido - Parseado como ', GUIDToString(GUIDPrueba))
      else
        WriteLn('Inválido');
    end;
    
    WriteLn;
    WriteLn('Extrayendo GUIDs del texto:');
    WriteLn('='*50);
    
    var TextoEjemplo := 'El ID de usuario es {8C2D56F0-8A1E-4B6C-A8D2-AF3A99B11F75} ' +
      'y el ID de sesión es 123e4567-e89b-12d3-a456-426614174000.';
    
    var GUIDsExtraidos := TValidadorGUID.ExtraerGUIDDeTexto(TextoEjemplo);
    
    WriteLn('Texto: ', TextoEjemplo);
    WriteLn('GUIDs encontrados:');
    for CadenaPrueba in GUIDsExtraidos do
      WriteLn('  - ', CadenaPrueba);
      
    ReadLn;
  except
    on E: Exception do
      WriteLn('Error: ', E.Message);
  end;
end.

Este ejemplo de validación demuestra validación completa de GUID usando expresiones regulares. La clase TValidadorGUID puede validar GUIDs en varios formatos (con/sin llaves, guiones o paréntesis), normalizarlos a un formato estándar y extraer GUIDs de bloques de texto más grandes. El patrón regex coincide con todas las representaciones comunes de GUID mientras que la función de normalización asegura un formato consistente. Esto es particularmente útil al procesar entrada de usuario o datos de sistemas externos.

Optimización del Rendimiento para Generación de GUID

Al generar GUIDs a escala en aplicaciones Delphi, el rendimiento se vuelve crítico. Aquí está cómo optimizar la generación de GUID y medir el rendimiento:

program RendimientoGUID;

{$APPTYPE CONSOLE}

uses
  System.SysUtils, System.Diagnostics, System.Classes, 
  System.Generics.Collections, System.Threading;

type
  TCacheGUID = class
  private
    FCache: TThreadList<TGUID>;
    FTamañoCache: Integer;
    FUmbralRellenado: Integer;
    
    procedure RellenarCache;
  public
    constructor Create(ATamañoCache: Integer = 1000);
    destructor Destroy; override;
    
    function ObtenerGUID: TGUID;
    property TamañoCache: Integer read FTamañoCache;
  end;

constructor TCacheGUID.Create(ATamañoCache: Integer);
begin
  inherited Create;
  FTamañoCache := ATamañoCache;
  FUmbralRellenado := ATamañoCache div 4;
  FCache := TThreadList<TGUID>.Create;
  RellenarCache;
end;

destructor TCacheGUID.Destroy;
begin
  FCache.Free;
  inherited;
end;

procedure TCacheGUID.RellenarCache;
var
  Lista: TList<TGUID>;
  i: Integer;
  NuevoGUID: TGUID;
begin
  Lista := FCache.LockList;
  try
    // Llenar cache a capacidad
    while Lista.Count < FTamañoCache do
    begin
      CreateGUID(NuevoGUID);
      Lista.Add(NuevoGUID);
    end;
  finally
    FCache.UnlockList;
  end;
end;

function TCacheGUID.ObtenerGUID: TGUID;
var
  Lista: TList<TGUID>;
begin
  Lista := FCache.LockList;
  try
    if Lista.Count = 0 then
      RellenarCache;
      
    Result := Lista.Last;
    Lista.Delete(Lista.Count - 1);
    
    // Rellenar cache asíncronamente si está por debajo del umbral
    if Lista.Count < FUmbralRellenado then
    begin
      TTask.Run(procedure
      begin
        RellenarCache;
      end);
    end;
  finally
    FCache.UnlockList;
  end;
end;

// Evaluar diferentes enfoques
procedure EvaluarGeneracionGUID(Cantidad: Integer);
var
  Cronometro: TStopwatch;
  GUIDs: TArray<TGUID>;
  i: Integer;
  Cache: TCacheGUID;
  
  procedure EvaluarEstandar;
  begin
    Cronometro := TStopwatch.StartNew;
    SetLength(GUIDs, Cantidad);
    
    for i := 0 to Cantidad - 1 do
      CreateGUID(GUIDs[i]);
      
    Cronometro.Stop;
    WriteLn(Format('CreateGUID estándar: %d ms para %d GUIDs (%.2f GUIDs/ms)',
      [Cronometro.ElapsedMilliseconds, Cantidad, 
       Cantidad / Max(1, Cronometro.ElapsedMilliseconds)]));
  end;
  
  procedure EvaluarConCache;
  begin
    Cache := TCacheGUID.Create(1000);
    try
      Cronometro := TStopwatch.StartNew;
      SetLength(GUIDs, Cantidad);
      
      for i := 0 to Cantidad - 1 do
        GUIDs[i] := Cache.ObtenerGUID;
        
      Cronometro.Stop;
      WriteLn(Format('Generación con cache: %d ms para %d GUIDs (%.2f GUIDs/ms)',
        [Cronometro.ElapsedMilliseconds, Cantidad,
         Cantidad / Max(1, Cronometro.ElapsedMilliseconds)]));
    finally
      Cache.Free;
    end;
  end;
  
  procedure EvaluarParalelo;
  begin
    Cronometro := TStopwatch.StartNew;
    SetLength(GUIDs, Cantidad);
    
    TParallel.For(0, Cantidad - 1, 
      procedure(Indice: Integer)
      begin
        CreateGUID(GUIDs[Indice]);
      end);
      
    Cronometro.Stop;
    WriteLn(Format('Generación paralela: %d ms para %d GUIDs (%.2f GUIDs/ms)',
      [Cronometro.ElapsedMilliseconds, Cantidad,
       Cantidad / Max(1, Cronometro.ElapsedMilliseconds)]));
  end;
  
begin
  WriteLn(Format('Evaluando generación de %d GUIDs...', [Cantidad]));
  WriteLn('='*60);
  
  EvaluarEstandar;
  EvaluarConCache;
  EvaluarParalelo;
  
  WriteLn;
  WriteLn('Uso de memoria:');
  WriteLn(Format('  Un solo GUID: %d bytes', [SizeOf(TGUID)]));
  WriteLn(Format('  %d GUIDs: %.2f MB', [Cantidad, (Cantidad * SizeOf(TGUID)) / (1024 * 1024)]));
end;

begin
  try
    // Probar con diferentes tamaños
    EvaluarGeneracionGUID(10000);
    WriteLn;
    EvaluarGeneracionGUID(100000);
    WriteLn;
    EvaluarGeneracionGUID(1000000);
    
    ReadLn;
  except
    on E: Exception do
      WriteLn('Error: ', E.Message);
  end;
end.

Este ejemplo de optimización del rendimiento demuestra tres enfoques para la generación de GUID de alto volumen: generación secuencial estándar, generación con cache con rellenado en segundo plano, y generación paralela usando TParallel. El enfoque con cache pre-genera GUIDs y rellena el cache asíncronamente, reduciendo la latencia para aplicaciones que necesitan GUIDs bajo demanda. La generación paralela aprovecha múltiples núcleos de CPU para operaciones masivas. Las evaluaciones ayudan a identificar el enfoque más adecuado para tu caso de uso específico. Ten en cuenta que CreateGUID es seguro para hilos, lo que hace que la generación paralela sea segura sin sincronización adicional.

Conclusión: Mejores Prácticas para Generación de UUID en Delphi

Al implementar la generación de UUID en aplicaciones Delphi, elige el método apropiado basado en tus requisitos. Para identificadores únicos de propósito general, CreateGUID proporciona una solución simple y multiplataforma. Para aplicaciones específicas de Windows que requieren compatibilidad COM, las llamadas directas a la API ofrecen más control. Los GUIDs secuenciales optimizan el rendimiento de la base de datos pero sacrifican algo de aleatoriedad. Los UUIDs deterministas permiten que los sistemas distribuidos generen identificadores consistentes de forma independiente. Siempre valida los GUIDs externos y considera el almacenamiento en caché para escenarios de alto rendimiento.

El desarrollo moderno con Delphi se beneficia del sólido soporte de GUID integrado en la RTL, con capacidades adicionales disponibles a través de las APIs de Windows e implementaciones personalizadas. Ya sea que estés construyendo aplicaciones de escritorio con VCL, aplicaciones multiplataforma con FireMonkey o aplicaciones de servidor, las técnicas presentadas en esta guía proporcionan una base sólida para la implementación de UUID. Recuerda considerar la seguridad de hilos, las implicaciones de rendimiento y los requisitos de almacenamiento al diseñar tu estrategia de UUID. Para aplicaciones de base de datos, evalúa si los GUIDs secuenciales proporcionan beneficios de rendimiento significativos para tu caso de uso específico.

Para generación rápida de UUID sin escribir código, siempre puedes usar nuestra herramienta generadora de UUID en línea, que proporciona generación instantánea de UUID en varios formatos con un simple clic.

© UUIDGenerator.co v1.0 All rights reserved (2025)