El trabajo no parecía especial: un servicio nocturno en Delphi que toma archivos hipotecarios escaneados, cuenta páginas y enruta cada archivo a la cola de procesamiento correcta. Funcionó en silencio 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 archivo, y en un servicio de 32 bits ese árbol empujó el working set por encima del techo de espacio de direcciones de 2 GB 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? Y responderla nunca requirió cargar el documento.
La Direct File API de HotPDF existe exactamente para esta clase de trabajo: operaciones PDF a nivel de archivo desde Delphi y C++Builder que leen desde disco lo que necesitan en lugar de materializar todo el modelo del documento. Saber a qué nivel de la API pertenece una operación determinada es 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 cuánto cuesta
Cargar un documento con LoadFromFile compra acceso aleatorio a todo: cualquier página, cualquier objeto, listo para reestructuración, edición de contenido o reserialización mediante SaveLoadedDocument. Ese poder es la herramienta correcta para manipulación de páginas: InsertPagesFromDocument y MovePage necesitan el árbol. El costo es proporcional al documento, no a la operación: el tiempo de análisis escala con el conteo de objetos, y la memoria residente corre como múltiplo del tamaño de archivo una vez consideradas las estructuras de objetos y los streams decodificados.
El desajuste aparece cuando los tamaños de entrada no tienen límite. Cargas de clientes, salida de escáneres y archivos de hace una década no respetan los supuestos de un corpus de prueba. Una tubería que carga cada entrada tiene requisitos de memoria determinados por el archivo más grande que alguien enviará alguna vez; una tubería que usa lecturas por handle para las preguntas que lo permiten tiene requisitos de memoria aproximadamente constantes. Para un servicio de larga duración, esa distinción 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 archivo de escala gigabyte todavía gasta segundos de CPU y un múltiplo del archivo en RAM para responder preguntas que la estructura del archivo podía contestar directamente. La concurrencia lo empeora: cuatro cargas grandes simultáneas compiten por el mismo presupuesto de memoria, así que el rendimiento colapsa justo cuando la cola está más ocupada.
Inspección basada en handles
El nivel de solo lectura abre el archivo 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 confiable este nivel. Revisen el handle: un retorno no positivo significa que la apertura falló, y llamar DAGetPageCount contra un handle muerto es el tipo de error que solo aparece en el archivo malformado que envía un cliente. Emparejen cada apertura exitosa con DACloseFile en un bloque finally, porque un servicio que fuga handles se degrada lentamente en lugar de fallar de forma visible. Y sepan qué cuesta el parámetro de contraseña: DAOpenFileReadOnly acepta una, pero para entradas cifradas vuelve internamente a un análisis completo para responder el conteo de páginas; la propiedad de memoria plana solo se mantiene para archivos sin cifrar, así que las entradas protegidas deberían pasar primero por DecryptFile.
El nivel de handle también sirve como puerta barata de triaje. Los archivos llegan de clientes mal etiquetados, truncados por cargas fallidas o renombrados desde otros formatos; una prueba con DAOpenFileReadOnly los rechaza en milisegundos en la puerta de entrada, con un error claro asociado al archivo correcto, en lugar de dejarlos fallar dentro de un worker de cola donde el diagnóstico cuesta una tarde.
Operaciones de archivo completo: copiar, descifrar, cifrar
El segundo nivel transforma archivos completos sin exponer sus interiores: los caballos de batalla de las tuberías de recepción.
// 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 administrado: abre e indexa la estructura PDF mientras avanza, así que una entrada truncada o que no es PDF falla aquí en lugar de tres etapas después. DecryptFile produce una copia descifrada y toma una ruta directa de reescritura AES-256 que evita construir el árbol de objetos cuando la entrada lo permite, la contraparte para archivos grandes del flujo de descifrado cargar-y-regrabar descrito en el artículo de cifrado AES-256. EncryptFile es la imagen espejo, aplica protección con contraseña durante una copia a nivel de archivo con los mismos parámetros de tipo de clave y permisos que usa la ruta en memoria.
Agregar 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 agregan después con una sección de referencias cruzadas que encadena hacia el original. Para un archivo de 900 MB que necesita una página agregada, el costo de escritura es el delta, no el archivo.
// 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 archivo original, porque los datos de referencias cruzadas agregados encadenan hacia offsets dentro de él. Y el modelo es append-only por diseño: cada guardado incremental agranda el archivo, nunca lo reduce, por lo que un documento que se estampa cada noche crece sin límite hasta que una reserialización periódica, cargar el documento y escribirlo de vuelta con SaveLoadedDocument, lo compacte. La propiedad append-only también es lo que hace que la actualización incremental sea la única forma segura de modificar documentos firmados digitalmente, una restricción examinada en el artículo de 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 archivo, 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 recuperable. Nunca usen actualizaciones incrementales para quitar contenido sensible; una reserialización completa, LoadFromFile seguido de SaveLoadedDocument, que conserva solo el estado actual, es la forma correcta de descartar 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 conviene codificarla como una decisión explícita de enrutamiento al inicio de una tubería en lugar de dejar que cada trabajo elija su camino:
- Contar, inspeccionar, clasificar: abrir un handle:
DAOpenFileReadOnly,DAGetPageCount,DACloseFile. - Mover, descifrar o cifrar archivos completos: llamadas a nivel de archivo:
DACopyFile,DecryptFile,EncryptFile. - Reestructurar páginas o fusionar documentos: carga completa:
LoadFromFile, luegoInsertPagesFromDocumentoMovePage, luegoSaveLoadedDocument. - Agregar un delta pequeño a un archivo enorme o firmado:
BeginIncrementalUpdatey guardar.
Las tuberías mixtas se benefician de un umbral de tamaño delante de la ruta de carga completa: enruten todo lo que supere algunos cientos de megabytes por los niveles Direct File y encolen el trabajo de reestructuración genuina para un worker de 64 bits con presupuesto de memoria. El umbral convierte las caídas por falta de memoria en una decisión de enrutamiento explícita y observable.
Cualquiera que sea el nivel que maneje un trabajo, dirijan su salida a un nombre temporal y renombren al destino solo después de validar el resultado; un archivo escrito a medias con el nombre final es indistinguible de uno bueno para la siguiente etapa de la tubería. Las llamadas Direct File hacen barata esa validación, porque confirmar la salida es en sí una prueba de handle de una línea.
FAQ: PDF grandes en servicios Delphi
¿Cómo obtengo el conteo de páginas de un PDF sin cargar todo el archivo?
DAOpenFileReadOnly más DAGetPageCount, como en el ejemplo de inspección anterior; el uso de memoria permanece plano sin importar el tamaño del archivo.
¿Por qué mi PDF crece después de cada guardado?
Las actualizaciones incrementales agregan por diseño; nada se elimina nunca. Compacten periódicamente con carga completa y regrabado, LoadFromFile y luego SaveLoadedDocument, cuando las revisiones acumuladas ya no sean necesarias.
¿La Direct File API abre PDF 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 plana que el resto de la tubería puede procesar a nivel de archivo.
La Direct File API se incluye 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í.