Contar las páginas de un archivo escaneado de 1,4 GB debería ser barato. Llame a LoadFromFile sobre ese archivo y deja de serlo: HotPDF analiza los datos de referencias cruzadas y construye un objeto en memoria por cada uno de los varios cientos de miles de objetos indirectos del documento, y un worker de 32 bits choca con el techo de 2 GB de espacio de direcciones en algún punto de ese análisis. La operación que quería, el recuento de páginas, nunca necesitó ninguno de esos objetos. Necesitaba el árbol de páginas y nada más. Esa brecha, entre lo que pide un trabajo y lo que entrega una carga completa, es toda la razón de ser de la Direct File API
La Direct File API da a Delphi y C++Builder acceso a nivel de archivo a un PDF: recuentos de páginas, copias, descifrado, anexos incrementales, todos leyendo de disco solo lo que realmente necesitan en lugar de reconstruir el modelo de documento completo en RAM. La habilidad consiste en emparejar cada trabajo con el nivel más ligero que pueda responderlo. Acierte con ese emparejamiento y un servicio mantiene la memoria plana con cualquier tamaño de entrada. Falle y el primer archivo sobredimensionado tumba al worker
Lo que le cuesta una carga completa
LoadFromFile no es el enemigo. Se gana su memoria: una vez que el árbol está en RAM tiene acceso aleatorio a cada página y cada objeto, que es exactamente lo que exigen InsertPagesFromDocument, MovePage y la reserialización mediante SaveLoadedDocument. No hay atajo para una reestructuración genuina; hay que tener el documento en la mano para reorganizarlo
El problema empieza cuando los tamaños de entrada no están bajo su control. Las subidas de clientes, la salida de escáneres y los archivos de hace una década ignoran lo que supusiera su corpus de pruebas. Cargue cada entrada incondicionalmente y su techo de memoria lo fija el archivo más grande que alguien llegue a enviar jamás. El tiempo de análisis sigue al número de objetos, y la memoria residente se asienta en varias veces el tamaño del archivo una vez contadas las estructuras de objetos y los streams decodificados, así que un gigabyte en disco puede significar varios gigabytes residentes
Recompilar para 64 bits eleva el techo del espacio de direcciones pero deja la factura intacta. El worker sigue quemando segundos de CPU y un múltiplo del archivo en RAM para responder una pregunta que la propia estructura del archivo podría haber respondido en milisegundos. Bajo concurrencia las cuentas se vuelven hostiles: cuatro cargas grandes ejecutándose a la vez comparten un único presupuesto de memoria, y el rendimiento se hunde precisamente cuando la cola es más profunda y menos puede permitírselo
Leer un archivo a través de un handle
El nivel de solo lectura abre un archivo como un handle, responde preguntas estructurales sobre él y lo cierra. Sin árbol de objetos, sin renderizado de páginas, sin memoria que crezca con la entrada
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 hábitos mantienen honesto este nivel. Primero, compruebe el valor de retorno. Un handle no positivo significa que la apertura falló, y disparar DAGetPageCount contra un handle muerto es la clase de fallo que permanece oculto hasta el día en que un cliente envía un archivo malformado. Segundo, empareje cada apertura correcta con DACloseFile dentro de un bloque finally; un servicio que pierde handles no se cae, simplemente se pudre, que es peor. Tercero, respete lo que hace realmente el parámetro de contraseña. DAOpenFileReadOnly acepta uno, pero para entradas cifradas cae en silencio a un análisis completo para leer el número de páginas, así que la garantía de memoria plana se evapora. Enrute primero los archivos protegidos a través de DecryptFile y el resto del pipeline sigue siendo barato
El mismo sondeo sirve también de puerta de triaje. Los archivos llegan mal etiquetados, subidos a medias o renombrados desde algún otro formato por completo, y una comprobación con DAOpenFileReadOnly rechaza todos esos en la puerta de entrada en milisegundos, con el error anclado al archivo culpable. La alternativa es dejar que un archivo basura se cuele hasta el fondo de un worker de cola y explote allí, donde desenredar qué entrada lo causó puede costar una tarde
Copiar, descifrar y cifrar archivos completos
El segundo nivel mueve y transforma archivos completos sin exponer nunca sus interioridades. Son las llamadas en las que más se apoyan los pipelines de recepción
// Copia estructural: validar y mover sin analizar el árbol de objetos
Status := Pdf.DACopyFile('incoming\statement.pdf', 'verified\statement.pdf');
LogDirectFileStatus('copy', Status);
// Descifrar al copiar: la ruta Direct File hacia entradas protegidas
Status := Pdf.DecryptFile('incoming\protected.pdf',
'verified\plain.pdf', 'batch-password');
LogDirectFileStatus('decrypt-copy', Status);
// Cifrar al copiar: proteger una salida sin una carga completa
Status := Pdf.EncryptFile('verified\statement.pdf',
'outbound\statement.pdf', 'owner-secret', '', aes256, [prPrint]);
LogDirectFileStatus('encrypt-copy', Status);
Cada llamada se gana su sitio. DACopyFile es la copia validada desde un directorio de cuarentena hacia el almacenamiento gestionado: abre e indexa la estructura del PDF a medida que avanza, así que una entrada truncada o que no es PDF falla justo aquí y no tres etapas más adelante. DecryptFile escribe una copia descifrada por una ruta de reescritura AES-256 directa que evita el árbol de objetos siempre que la entrada lo permite, la contrapartida para archivos grandes del flujo de descifrado cargar-y-volver-a-guardar tratado en el artículo sobre cifrado AES-256. EncryptFile ejecuta el mismo movimiento a la inversa, aplicando protección por contraseña durante una copia a nivel de archivo con los mismos parámetros de tipo de clave y permisos que ya 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 se quedan donde están en disco, y cualquier objeto nuevo o modificado se anexa después de ellos, seguido de una nueva sección de referencias cruzadas que encadena de vuelta al original. Para un archivo de 900 MB que necesita una sola página añadida, el coste de escritura es el delta, no el archivo completo
// Anexar una página de auditoría a un archivo grande sin reescribirlo
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'); // bytes originales + delta
Aquí importan dos puntos de disciplina. BeginIncrementalUpdate tiene que apuntar al archivo original, ya que los datos de referencias cruzadas anexados encadenan de vuelta a desplazamientos de bytes dentro de él. Y el modelo es de solo anexar por diseño: cada guardado incremental hace crecer el archivo, nunca lo encoge. Un documento sellado cada noche se hinchará sin límite hasta que una reserialización periódica, cargándolo y volviéndolo a escribir mediante SaveLoadedDocument, lo compacte. Esa misma naturaleza de solo anexar es lo que convierte la actualización incremental en la única forma segura de tocar un documento firmado digitalmente, una restricción examinada en el artículo sobre firmas digitales y PAdES. La maquinaria subyacente de referencias cruzadas recibe su propio tratamiento en el artículo sobre flujos de objetos y actualizaciones incrementales
Hay una trampa en los guardados de solo anexar que se cuela en la mayoría de las revisiones. Los bytes originales permanecen en el archivo, legibles para cualquiera dispuesto a mirar. Una actualización incremental que "sustituye" una página no borra la antigua; la reemplaza en la revisión actual mientras la revisión anterior sigue ahí, totalmente recuperable. Así que las actualizaciones incrementales son la herramienta equivocada para eliminar contenido sensible. Para descartar de verdad un historial que el destinatario nunca debería ver, necesita una reserialización completa: LoadFromFile seguido de SaveLoadedDocument, que escribe solo el estado actual y deja atrás las revisiones enterradas
Emparejar el nivel con la operación
La lógica de selección es lo bastante corta para tenerla en la cabeza, y compensa codificarla como una decisión de enrutado explícita al principio del pipeline en lugar de dejar que cada trabajo improvise su propio camino. La operación que necesita decide el nivel:
- Contar, inspeccionar o clasificar abre un handle:
DAOpenFileReadOnly,DAGetPageCount,DACloseFile - Mover, descifrar o cifrar un archivo completo se queda a nivel de archivo con
DACopyFile,DecryptFileoEncryptFile - Reestructurar páginas o combinar documentos necesita la carga completa:
LoadFromFile, despuésInsertPagesFromDocumentoMovePage, despuésSaveLoadedDocument - Añadir un delta pequeño a un archivo enorme o firmado llama a
BeginIncrementalUpdatey guarda
Los pipelines mixtos hacen bien en poner un umbral de tamaño delante de la ruta de carga completa. Envíe todo lo que pase de unos cientos de megabytes por los niveles Direct File, y reserve la carga completa para reestructuraciones genuinas en un worker de 64 bits con un presupuesto de memoria real. El umbral convierte un fallo por falta de memoria en una decisión de enrutado que puede ver y ajustar
Sea cual sea el nivel que atienda un trabajo, escriba su salida con un nombre temporal y renómbrela a su sitio solo cuando el resultado se haya validado. Un archivo a medio escribir bajo el nombre definitivo parece exactamente uno bueno a ojos de la siguiente etapa del pipeline, y las llamadas Direct File hacen barata la comprobación: confirmar una salida es un sondeo de handle de una línea
La Direct File API se incluye como parte del HotPDF Delphi 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í