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