Contare le pagine di un archivio scansionato da 1,4 GB dovrebbe costare poco. Chiamate LoadFromFile su quel file e smette di costare poco: HotPDF analizza i dati dei riferimenti incrociati e costruisce un oggetto in memoria per ciascuno delle diverse centinaia di migliaia di oggetti indiretti del documento, e un processo a 32 bit sbatte contro il tetto dei 2 GB di spazio di indirizzamento a metà di quella analisi. L'operazione che volevate, il conteggio delle pagine, non ha mai avuto bisogno di nessuno di quegli oggetti. Le serviva l'albero delle pagine e nient'altro. Quello scarto, fra ciò che un lavoro chiede e ciò che un caricamento completo consegna, è l'intera ragione per cui esiste la Direct File API
La Direct File API dà a Delphi e C++Builder accesso a un PDF a livello di file: conteggio pagine, copie, decifratura, aggiunte incrementali, tutto leggendo da disco solo ciò che serve davvero anziché ricostruire in RAM l'intero modello del documento. L'abilità sta nell'accoppiare ogni lavoro al livello più leggero capace di rispondere. Azzeccate l'accoppiamento e un servizio mantiene la memoria piatta con qualunque dimensione di ingresso. Sbagliatelo e il primo file fuori misura porta giù il processo
Quanto vi costa un caricamento completo
LoadFromFile non è il nemico. Si guadagna la memoria che consuma: una volta che l'albero è in RAM avete accesso casuale a ogni pagina e a ogni oggetto, che è esattamente ciò che InsertPagesFromDocument, MovePage e la riserializzazione tramite SaveLoadedDocument richiedono. Per una ristrutturazione autentica non ci sono scorciatoie: dovete tenere il documento per riorganizzarlo
I guai iniziano quando le dimensioni degli ingressi non dipendono da voi. Caricamenti dei clienti, uscite di scanner e archivi di dieci anni fa ignorano qualunque cosa il vostro corpus di prova desse per scontata. Caricate ogni ingresso senza condizioni e il vostro tetto di memoria lo fissa il singolo file più grande che qualcuno invierà mai. Il tempo di analisi segue il numero di oggetti, e la memoria residente si assesta su parecchie volte la dimensione del file una volta contate le strutture degli oggetti e gli stream decodificati, quindi un gigabyte su disco può significare più gigabyte residenti
Ricompilare a 64 bit alza il tetto dello spazio di indirizzamento ma lascia il conto intatto. Il processo brucia comunque secondi di CPU e un multiplo del file in RAM per rispondere a una domanda a cui la struttura stessa del file avrebbe potuto rispondere in millisecondi. Sotto concorrenza la matematica diventa ostile: quattro caricamenti grandi in esecuzione insieme condividono un solo budget di memoria, e la produttività crolla proprio quando la coda è più profonda e ve lo potete permettere meno
Leggere un file attraverso un handle
Il livello in sola lettura apre un file come handle, risponde a domande strutturali su di esso e lo chiude. Nessun albero di oggetti, nessun rendering di pagina, nessuna memoria che cresce con l'ingresso
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;
Tre abitudini tengono onesto questo livello. Primo, controllate il valore restituito. Un handle non positivo significa che l'apertura è fallita, e scagliare DAGetPageCount contro un handle morto è il tipo di bug che resta nascosto fino al giorno in cui un cliente invia un file malformato. Secondo, accoppiate ogni apertura riuscita a DACloseFile dentro un blocco finally; un servizio che perde handle non va in crash, marcisce e basta, il che è peggio. Terzo, rispettate ciò che il parametro della password fa davvero. DAOpenFileReadOnly ne accetta una, ma per gli ingressi cifrati ripiega in silenzio su una analisi completa per leggere il conteggio delle pagine, quindi la garanzia di memoria piatta svanisce. Instradate prima i file protetti attraverso DecryptFile e il resto della catena resta economico
La stessa sonda funge anche da porta di smistamento. I file arrivano etichettati male, caricati a metà o rinominati partendo da tutt'altro formato, e un controllo con DAOpenFileReadOnly respinge tutto ciò all'ingresso in millisecondi, con l'errore agganciato al file colpevole. L'alternativa è lasciare che un file spazzatura entri in profondità in un processo di coda ed esploda lì, dove districare quale ingresso lo ha causato può costare un pomeriggio
Copiare, decifrare e cifrare interi file
Il secondo livello sposta e trasforma file completi senza mai esporne le viscere. Sono le chiamate su cui le catene di acquisizione si appoggiano di più
// Copia strutturale: verifica e sposta senza analizzare l'albero degli oggetti
Status := Pdf.DACopyFile('incoming\statement.pdf', 'verified\statement.pdf');
LogDirectFileStatus('copy', Status);
// Decifra copiando: la via Direct File verso gli ingressi protetti
Status := Pdf.DecryptFile('incoming\protected.pdf',
'verified\plain.pdf', 'batch-password');
LogDirectFileStatus('decrypt-copy', Status);
// Cifra copiando: protegge una uscita senza un caricamento completo
Status := Pdf.EncryptFile('verified\statement.pdf',
'outbound\statement.pdf', 'owner-secret', '', aes256, [prPrint]);
LogDirectFileStatus('encrypt-copy', Status);
Ogni chiamata si guadagna il posto. DACopyFile è la copia verificata da una directory di quarantena verso lo storage gestito: apre e indicizza la struttura del PDF mentre procede, così un ingresso troncato o non PDF fallisce proprio qui invece che tre stadi più a valle. DecryptFile scrive una copia decifrata lungo un percorso diretto di riscrittura AES-256 che salta l'albero degli oggetti ogni volta che l'ingresso lo permette, la controparte per file grandi del flusso di decifratura carica-e-risalva trattato nell'articolo sulla cifratura AES-256. EncryptFile compie lo stesso movimento al contrario, applicando la protezione con password durante una copia a livello di file con gli stessi parametri di tipo chiave e permessi che usa il percorso in memoria
Accodare le modifiche anziché riscrivere
L'aggiornamento incrementale, definito in ISO 32000-1 §7.5.6, è il terzo livello. I byte originali restano dove sono su disco, e gli oggetti nuovi o modificati vengono accodati dopo di essi, seguiti da una nuova sezione di riferimenti incrociati che si concatena all'originale. Per un archivio da 900 MB a cui serve una sola pagina in più, il costo di scrittura è il delta, non l'intero file
// Accoda una pagina di audit a un grande archivio senza riscriverlo
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'); // byte originali + delta
Qui contano due punti di disciplina. BeginIncrementalUpdate deve puntare al file originale, dato che i dati dei riferimenti incrociati accodati si concatenano a offset di byte interni a esso. E il modello è a sola aggiunta per progetto: ogni salvataggio incrementale fa crescere il file, non lo rimpicciolisce mai. Un documento timbrato ogni notte si gonfierà senza limite finché una riserializzazione periodica, che lo carica e lo riscrive con SaveLoadedDocument, non lo compatta. Quella stessa natura a sola aggiunta è ciò che rende l'aggiornamento incrementale l'unico modo sicuro di toccare un documento firmato digitalmente, un vincolo esaminato nell'articolo su firme digitali e PAdES. La macchina sottostante dei riferimenti incrociati ha una trattazione propria nell'articolo su object stream e aggiornamenti incrementali
Nei salvataggi a sola aggiunta c'è una trappola che sfugge alla maggior parte delle revisioni. I byte originali restano nel file, leggibili da chiunque abbia voglia di guardare. Un aggiornamento incrementale che «sostituisce» una pagina non cancella quella vecchia; la scavalca nella revisione corrente mentre la revisione precedente resta lì, pienamente recuperabile. Gli aggiornamenti incrementali sono quindi lo strumento sbagliato per togliere contenuto riservato. Per eliminare davvero una storia che un destinatario non deve mai vedere serve una riserializzazione completa: LoadFromFile seguito da SaveLoadedDocument, che scrive soltanto lo stato corrente e si lascia indietro le revisioni sepolte
Accoppiare il livello all'operazione
La logica di scelta è abbastanza breve da tenerla a mente, e conviene codificarla come una decisione di instradamento esplicita in cima alla catena invece di lasciare che ogni lavoro improvvisi il proprio percorso. L'operazione che vi serve decide il livello:
- Contare, ispezionare o classificare apre un handle:
DAOpenFileReadOnly,DAGetPageCount,DACloseFile - Spostare, decifrare o cifrare un intero file resta a livello di file con
DACopyFile,DecryptFileoEncryptFile - Ristrutturare le pagine o unire documenti richiede il caricamento completo:
LoadFromFile, poiInsertPagesFromDocumentoMovePage, poiSaveLoadedDocument - Aggiungere un piccolo delta a un file enorme o firmato chiama
BeginIncrementalUpdatee salva
Le catene miste fanno bene a mettere una soglia di dimensione davanti al percorso di caricamento completo. Mandate qualunque cosa superi qualche centinaio di megabyte attraverso i livelli Direct File, e riservate il caricamento completo alla ristrutturazione autentica su un processo a 64 bit con un budget di memoria vero. La soglia converte un crash per memoria esaurita in una decisione di instradamento che potete vedere e regolare
Qualunque livello gestisca un lavoro, scrivete la sua uscita su un nome temporaneo e rinominatela al posto giusto solo quando il risultato è stato verificato. Un file scritto a metà che siede sotto il nome definitivo sembra identico a uno buono per lo stadio successivo della catena, e le chiamate Direct File rendono economico il controllo: confermare una uscita è una sonda su handle di una riga
La Direct File API è distribuita come parte dello HotPDF Delphi Component per Delphi e C++Builder. La pagina di prodotto rimanda al riferimento completo delle funzioni, comprese le chiamate di aggiornamento incrementale mostrate qui