Å telle sidene i et skannet arkiv på 1,4 GB burde være billig. Kall LoadFromFile på den filen, og det slutter å være billig: HotPDF parser kryssreferansedataene og bygger et objekt i minnet for hvert eneste av dokumentets flere hundre tusen indirekte objekter, og en 32-bit arbeidsprosess treffer taket på 2 GB adresserom et sted midt i den parsingen. Operasjonen du ønsket deg, sidetallet, trengte aldri noen av disse objektene. Den trengte kun sidetreet og ingenting annet. Det gapet, mellom det en jobb ber om og det en full innlasting leverer, er hele grunnen til at Direct File API-et finnes
Direct File API-et gir Delphi og C++Builder filnivå-tilgang til en PDF: sidetall, kopier, dekryptering, inkrementelle tillegg, alt sammen ved å lese fra disk akkurat det de faktisk trenger i stedet for å rekonstruere hele dokumentmodellen i RAM. Kunsten er å matche hver jobb med det letteste nivået som kan besvare den. Får du den matchingen riktig, holder en tjeneste flatt minneforbruk uansett inndatastørrelse. Får du den feil, tar den første overdimensjonerte filen ned arbeidsprosessen
Hva en full innlasting koster deg
LoadFromFile er ikke fienden. Den fortjener minnebruken sin: når treet er i RAM, har du tilfeldig tilgang til hver side og hvert objekt, noe som er akkurat det InsertPagesFromDocument, MovePage og reserialisering gjennom SaveLoadedDocument krever. Det finnes ingen snarvei for genuin restrukturering; du må holde dokumentet for å omorganisere det
Problemene starter når inndatastørrelser ikke er dine å kontrollere. Kundeopplastinger, skanneroutput og arkiver fra et tiår tilbake bryr seg ikke om hva testkorpuset ditt antok. Laster du inn hver inndata ubetinget, blir minnetaket ditt satt av den enkeltstørste filen noen noensinne vil sende inn. Parsetiden følger objektantallet, og residentminnet stabiliserer seg på flere ganger filstørrelsen etter at objektstrukturer og dekodede strømmer er talt med, så én gigabyte på disk kan bety flere gigabyte resident
Å rekompilere til 64-bit hever taket på adresserommet, men lar selve regningen stå urørt. Arbeidsprosessen brenner fortsatt sekunder med CPU-tid og et multiplum av filen i RAM for å besvare et spørsmål filens egen struktur kunne ha besvart på millisekunder. Under samtidighet blir regnestykket fiendtlig: fire store innlastinger som kjører samtidig, deler ett minnebudsjett, og gjennomstrømningen kollapser akkurat når køen er dypest og du har minst råd til det
Å lese en fil gjennom et handle
Det skrivebeskyttede nivået åpner en fil som et handle, svarer på strukturelle spørsmål om den, og lukker den. Ikke noe objekttre, ingen siderendring, ikke noe minneforbruk som vokser med inndataen
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 vaner holder dette nivået ærlig. Først: sjekk returverdien. Et ikke-positivt handle betyr at åpningen feilet, og å avfyre DAGetPageCount mot et dødt handle er den typen feil som holder seg skjult til den dagen en kunde sender en feilformet fil. For det andre: par hver vellykket åpning med DACloseFile inne i en finally-blokk; en tjeneste som lekker handles, krasjer ikke, den bare råtner, noe som er verre. For det tredje: respekter hva passordparameteren faktisk gjør. DAOpenFileReadOnly tar imot en, men for krypterte inndata faller den stille tilbake til en full parsing for å lese sidetallet, så garantien om flatt minneforbruk fordamper. Rut beskyttede filer gjennom DecryptFile først, så forblir resten av pipelinen billig
Den samme sonderingen fungerer også som en triage-port. Filer dukker opp feilmerket, halvveis opplastet, eller omdøpt fra et helt annet format, og en DAOpenFileReadOnly-sjekk avviser alle disse ved inngangsdøren i løpet av millisekunder, med feilen festet til den skyldige filen. Alternativet er å la en søppelfil ri dypt inn i en køarbeidsprosess og eksplodere der, hvor det å nøste opp hvilken inndata som forårsaket det, kan koste en ettermiddag
Å kopiere, dekryptere og kryptere hele filer
Det andre nivået flytter og transformerer komplette filer uten noensinne å eksponere det indre av dem. Dette er kallene mottakspipeliner lener seg mest på
// Strukturell kopi: valider-og-flytt uten å parse objekttreet
Status := Pdf.DACopyFile('incoming\statement.pdf', 'verified\statement.pdf');
LogDirectFileStatus('copy', Status);
// Dekrypter under kopiering: Direct File-veien inn i beskyttede inndata
Status := Pdf.DecryptFile('incoming\protected.pdf',
'verified\plain.pdf', 'batch-password');
LogDirectFileStatus('decrypt-copy', Status);
// Krypter under kopiering: beskytt en output uten en full innlasting
Status := Pdf.EncryptFile('verified\statement.pdf',
'outbound\statement.pdf', 'owner-secret', '', aes256, [prPrint]);
LogDirectFileStatus('encrypt-copy', Status);
Hvert kall fortjener sin plass. DACopyFile er den validerte kopien fra en karantenekatalog inn i administrert lagring: den åpner og indekserer PDF-strukturen underveis, så en avkuttet eller ikke-PDF-inndata feiler akkurat her i stedet for tre trinn lenger nede i pipelinen. DecryptFile skriver en dekryptert kopi langs en direkte AES-256-omskrivingsvei som hopper over objekttreet når inndataen tillater det, storfil-motstykket til flyten for last-og-lagre-på-nytt-dekryptering som dekkes i artikkelen om AES-256-kryptering. EncryptFile kjører den samme bevegelsen omvendt, og påfører passordbeskyttelse under en kopiering på filnivå med de samme nøkkeltype- og tillatelsesparameterne den minnebaserte veien allerede bruker
Å legge til endringer i stedet for å skrive om
Inkrementell oppdatering, definert i ISO 32000-1 §7.5.6, er det tredje nivået. De opprinnelige bytene blir liggende der de er på disk, og eventuelle nye eller endrede objekter legges til etter dem, fulgt av en fersk kryssreferanseseksjon som kjeder seg tilbake til originalen. For et arkiv på 900 MB som trenger én enkelt side lagt til, er skrivekostnaden lik deltaet, ikke hele filen
// Legg til en revisjonsside i et stort arkiv uten å skrive det om
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'); // opprinnelige byte + delta
To disiplinpunkter betyr noe her. BeginIncrementalUpdate må peke på originalfilen, siden de tillagte kryssreferansedataene kjeder seg tilbake til byteoffsetter inne i den. Og modellen er kun-tillegg av design: hver inkrementell lagring gjør filen større, aldri mindre. Et dokument som stemples hver natt, vil vokse uten grense inntil en periodisk reserialisering, ved å laste det inn og skrive det tilbake gjennom SaveLoadedDocument, komprimerer det ned igjen. Den samme kun-tillegg-naturen er det som gjør inkrementell oppdatering til den eneste trygge måten å røre et digitalt signert dokument på, en begrensning som gjennomgås i artikkelen om digitale signaturer og PAdES. Det underliggende kryssreferansemaskineriet får sin egen behandling i artikkelen om objektstrømmer og inkrementelle oppdateringer
Det finnes en felle i kun-tillegg-lagringer som glipper forbi de fleste gjennomganger. De opprinnelige bytene blir liggende i filen, lesbare for alle som vil se etter. En inkrementell oppdatering som «erstatter» en side, sletter ikke den gamle; den overstyrer den i gjeldende revisjon mens den forrige revisjonen fortsatt ligger der, fullt gjenopprettbar. Så inkrementelle oppdateringer er feil verktøy for å fjerne sensitivt innhold. For genuint å fjerne historikk en mottaker aldri skal se, trenger du en full reserialisering: LoadFromFile etterfulgt av SaveLoadedDocument, som skriver ut kun gjeldende tilstand og lar de begravde revisjonene bli liggende igjen
Å matche nivået til operasjonen
Utvelgelseslogikken er kort nok til å holde i hodet, og det lønner seg å kode den som en eksplisitt rutingbeslutning øverst i en pipeline i stedet for å la hver jobb improvisere sin egen vei. Operasjonen du trenger, avgjør nivået:
- Telle, inspisere eller klassifisere åpner et handle:
DAOpenFileReadOnly,DAGetPageCount,DACloseFile - Å flytte, dekryptere eller kryptere en hel fil holder seg på filnivå med
DACopyFile,DecryptFileellerEncryptFile - Å restrukturere sider eller slå sammen dokumenter trenger den fulle innlastingen:
LoadFromFile, deretterInsertPagesFromDocumentellerMovePage, deretterSaveLoadedDocument - Å legge til et lite delta i en enorm eller signert fil kaller
BeginIncrementalUpdateog lagrer
Blandede pipeliner gjør lurt i å legge en størrelsesterskel foran den fulle innlastingsveien. Send alt over noen hundre megabyte gjennom Direct File-nivåene, og reserver den fulle innlastingen for genuin restrukturering på en 64-bit arbeidsprosess med et reelt minnebudsjett. Terskelen omgjør et tomt-for-minne-krasj til en rutingbeslutning du kan se og justere
Uansett hvilket nivå som håndterer en jobb, skriv outputen til et midlertidig navn og gi den det endelige navnet først når resultatet er validert. En halvskrevet fil som ligger under det endelige navnet, ser akkurat ut som en god en for neste trinn i pipelinen, og Direct File-kallene gjør sjekken billig: å bekrefte en output er en énlinjes handle-sondering
Direct File API-et følger med som en del av HotPDF Delphi Component for Delphi og C++Builder. Produktsiden lenker til den fullstendige funksjonsreferansen, inkludert de inkrementelle oppdateringskallene som vises her