El trabajo no tenía nada de especial: un servicio nocturno Delphi que toma archivos hipotecarios escaneados, cuenta páginas y envía cada fichero a la cola de procesamiento adecuada. Funcionó discretamente durante meses, hasta que llegó un archivo de 1,4 GB. LoadFromFile analiza los datos de referencias cruzadas y materializa un objeto por cada uno de los varios cientos de miles de objetos indirectos del fichero, y en un servicio de 32 bits ese árbol empujó el conjunto de trabajo más allá del techo de 2 GB de espacio de direcciones a mitad del análisis. La solución no fue un servidor más grande. El trabajo solo hacía una pregunta: ¿cuántas páginas hay? Responderla nunca exigió cargar el documento
La Direct File API de HotPDF existe exactamente para esta clase de trabajo: operaciones PDF a nivel de fichero desde Delphi y C++Builder que leen de disco lo que necesitan en lugar de materializar todo el modelo de documento. Saber a qué nivel de la API pertenece una operación concreta marca la diferencia entre un servicio con uso de memoria plano y uno que cae ante la primera entrada sobredimensionada
Qué aporta la carga completa y qué cuesta
Cargar un documento con LoadFromFile aporta acceso aleatorio a todo: cualquier página, cualquier objeto, listo para reestructuración, edición de contenido o reserialización mediante SaveLoadedDocument. Esa potencia es la herramienta adecuada para manipular páginas: InsertPagesFromDocument y MovePage necesitan el árbol. El coste es proporcional al documento, no a su operación: el tiempo de análisis escala con el número de objetos, y la memoria residente alcanza un múltiplo del tamaño del fichero cuando se tienen en cuenta estructuras de objetos y streams decodificados
El desajuste aparece cuando los tamaños de entrada no tienen límite. Subidas de clientes, salidas de escáner y archivos de hace diez años no respetan las suposiciones de un corpus de pruebas. Una tubería que carga todas las entradas tiene requisitos de memoria fijados por el fichero más grande que alguien vaya a enviar jamás; una tubería que usa lecturas basadas en handles para las preguntas que lo permiten tiene requisitos de memoria aproximadamente constantes. Para un servicio de larga ejecución, esa diferencia importa más que la velocidad bruta
Mover el servicio a 64 bits eleva el techo, pero no cambia la economía: un worker que analiza un fichero de escala gigabyte sigue gastando segundos de CPU y un múltiplo del fichero en RAM para contestar preguntas que la estructura del fichero podía contestar directamente. La concurrencia lo empeora: cuatro cargas grandes simultáneas compiten por el mismo presupuesto de memoria, así que el rendimiento se hunde justo cuando la cola está más cargada
Inspección basada en handles
El nivel de solo lectura abre el fichero como handle, responde preguntas estructurales y lo cierra: sin árbol de objetos, sin renderizado de páginas, sin memoria proporcional
var
Pdf: THotPDF;
Handle, PageCount: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Handle := Pdf.DAOpenFileReadOnly('archive-2026-06.pdf', '');
if Handle > 0 then
try
PageCount := Pdf.DAGetPageCount(Handle);
RouteByPageCount('archive-2026-06.pdf', PageCount);
finally
Pdf.DACloseFile(Handle);
end;
finally
Pdf.Free;
end;
end;
Tres reglas mantienen fiable este nivel. Compruebe el handle: un retorno no positivo significa que la apertura falló, y llamar a DAGetPageCount contra un handle muerto es el tipo de error que solo aparece con el fichero malformado que envía un cliente. Empareje cada apertura correcta con DACloseFile en un bloque finally, porque un servicio que filtra handles se degrada lentamente en vez de fallar de forma visible. Y conozca el coste del parámetro de contraseña: DAOpenFileReadOnly acepta una, pero para entradas cifradas recurre internamente a un análisis completo para responder al recuento de páginas; la propiedad de memoria plana solo se mantiene para ficheros sin cifrar, así que las entradas protegidas deberían pasar primero por DecryptFile
El nivel de handles también proporciona una puerta de triaje barata. Los ficheros llegan de clientes mal etiquetados, truncados por subidas fallidas o renombrados desde otros formatos; una prueba con DAOpenFileReadOnly los rechaza en milisegundos en la entrada, con un error claro asociado al fichero correcto, en lugar de dejarlos fallar dentro de un worker de cola donde el diagnóstico cuesta una tarde
Operaciones de fichero completo: copiar, descifrar, cifrar
El segundo nivel transforma ficheros completos sin exponer sus entrañas: los caballos de batalla de las tuberías de entrada
// Structural copy: validate-and-move without parsing the object tree
Status := Pdf.DACopyFile('incoming\statement.pdf', 'verified\statement.pdf');
LogDirectFileStatus('copy', Status);
// Decrypt while copying: the Direct File route into protected inputs
Status := Pdf.DecryptFile('incoming\protected.pdf',
'verified\plain.pdf', 'batch-password');
LogDirectFileStatus('decrypt-copy', Status);
// Encrypt while copying: protect an output without a full load
Status := Pdf.EncryptFile('verified\statement.pdf',
'outbound\statement.pdf', 'owner-secret', '', aes256, [prPrint]);
LogDirectFileStatus('encrypt-copy', Status);
Cada llamada tiene un papel distinto. DACopyFile es la copia validada desde un directorio de cuarentena hacia almacenamiento gestionado: abre e indexa la estructura PDF durante el proceso, de modo que una entrada truncada o no PDF falla aquí y no tres fases más tarde. DecryptFile produce una copia descifrada, tomando una ruta directa de reescritura AES-256 que evita construir el árbol de objetos siempre que la entrada lo permite, el equivalente para ficheros grandes del flujo de descifrar con carga y regrabado descrito en el artículo sobre cifrado AES-256. EncryptFile es la imagen especular, aplicando protección por contraseña durante una copia a nivel de fichero con el mismo tipo de clave y parámetros de permisos que usa la ruta en memoria
Anexar cambios en lugar de reescribir
La actualización incremental, definida en ISO 32000-1 §7.5.6, es el tercer nivel: los bytes originales permanecen intactos en disco, y los objetos modificados o nuevos se anexan tras ellos con una sección de referencias cruzadas que encadena hacia la original. Para un archivo de 900 MB que necesita una página añadida, el coste de escritura es el delta, no el fichero
// Append an audit page to a large archive without rewriting it
Pdf.BeginIncrementalUpdate('archive-2026-06.pdf');
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Processed by intake service 2026-06-11');
Pdf.SaveIncrementalUpdate('archive-2026-06-stamped.pdf'); // original bytes + delta
La disciplina aquí: BeginIncrementalUpdate debe apuntar al fichero original, porque los datos de referencias cruzadas anexados encadenan hacia offsets dentro de él. Y el modelo es append-only por diseño: cada guardado incremental aumenta el tamaño del fichero, nunca lo reduce, así que un documento que se sella cada noche crece sin límite hasta que una reserialización periódica, cargar el documento y volver a escribirlo mediante SaveLoadedDocument, lo compacta. La propiedad append-only también es lo que convierte la actualización incremental en la única forma segura de modificar documentos firmados digitalmente, una restricción examinada en el artículo sobre firmas digitales y PAdES; la maquinaria subyacente de referencias cruzadas se cubre en el artículo sobre object streams y actualizaciones incrementales
Una propiedad de los guardados append-only es fácil de pasar por alto en revisión: los bytes originales permanecen en el fichero, legibles para cualquiera que mire. Una actualización incremental que "reemplaza" una página no borra la anterior: la sustituye en la revisión actual mientras la revisión previa sigue siendo recuperable. No use nunca actualizaciones incrementales para eliminar contenido sensible; una reserialización completa, LoadFromFile seguido de SaveLoadedDocument, que solo lleva el estado actual, es la forma correcta de desprenderse de historia que un destinatario no debe ver
Hacer coincidir el nivel con la operación
La lógica de selección se comprime en cuatro líneas, y merece codificarse como decisión explícita de enrutamiento al principio de una tubería en lugar de dejar que cada trabajo elija su propio camino
- Contar, inspeccionar, clasificar: abra un handle:
DAOpenFileReadOnly,DAGetPageCount,DACloseFile - Mover, descifrar o cifrar ficheros completos: llamadas a nivel de fichero:
DACopyFile,DecryptFile,EncryptFile - Reestructurar páginas o fusionar documentos: carga completa:
LoadFromFile, luegoInsertPagesFromDocumentoMovePage, y despuésSaveLoadedDocument - Añadir un delta pequeño a un fichero enorme o firmado:
BeginIncrementalUpdatey guardar
Las tuberías mixtas se benefician de un umbral de tamaño delante de la ruta de carga completa: envíe todo lo que supere unos cientos de megabytes por los niveles Direct File y reserve el trabajo de reestructuración genuina para un worker de 64 bits con presupuesto de memoria. El umbral convierte los fallos por falta de memoria en una decisión de enrutamiento explícita y observable
Sea cual sea el nivel que atienda el trabajo, envíe su salida a un nombre temporal y renómbrela a su ubicación final solo después de validar el resultado; un fichero a medio escribir bajo el nombre final es indistinguible de uno correcto para la siguiente fase de la tubería. Las llamadas Direct File abaratan esa validación, porque confirmar la salida es en sí una prueba de handle de una línea
FAQ: PDFs grandes en servicios Delphi
¿Cómo obtengo el recuento de páginas de un PDF sin cargar todo el fichero?
DAOpenFileReadOnly más DAGetPageCount, como en el ejemplo de inspección anterior; el uso de memoria permanece plano con independencia del tamaño del fichero
¿Por qué mi PDF crece después de cada guardado?
Las actualizaciones incrementales anexan por diseño; nunca se elimina nada. Compacte periódicamente con una carga completa y regrabado, LoadFromFile y luego SaveLoadedDocument, cuando no se necesiten las revisiones acumuladas
¿La Direct File API abre PDFs cifrados?
Acepta una contraseña, pero las entradas cifradas se enrutan internamente por un análisis completo y pierden la ventaja de memoria plana. Para entradas protegidas, DecryptFile con la contraseña produce una copia sin cifrar que el resto de la tubería puede procesar a nivel de fichero
La Direct File API se entrega como parte de HotPDF Component para Delphi y C++Builder; la página del producto enlaza la referencia completa de funciones, incluidas las llamadas de actualización incremental mostradas aquí