PDFlibPas kan åpne en lokal PDF gjennom en avgrenset skrivebeskyttet minnekartlagt visning: LoadFromMappedFile og DAOpenMappedFile holder nøyaktig ett skyvevindu over filen, kartlegger det på nytt ved behov og leverer hvert objektsnitt gjennom lesing på absolutt offset. PDF-biblioteket for Delphi holder aldri hele kilden i minnet, så bruken av adresseområdet forblir flat når filen vokser. Designet er laget for én arbeidsmengde: PDF-er på gigabyte-størrelse der parseren er ferdig med å laste inn og fortsatt går tilbake til disken, objekt for objekt og strømfragment for strømfragment
Hvorfor er spredte lesinger fortsatt dyre etter at PDF-en er lastet?
Å laste inn en PDF betyr ikke at lesingen er ferdig, og i en fil på flere gigabyte er det dette gapet som koster tid. En kryssreferansetabell eller kryssreferansestrøm (ISO 32000-1 §7.5.4 og §7.5.8) registrerer bare hvor hvert indirekte objekt begynner. Byte-ene kommer senere, når en side rendres, et fontprogram dekodes eller en innebygd filstrøm (ISO 32000-1 §7.11.4) trekkes ut. Et arkiv på 2 GB med titusenvis av objekter blir til titusenvis av små, uordnede lesinger, og ingen av dem er kjent ved innlasting
Disse lesingene gikk tidligere gjennom en delt Seek fulgt av Read på én posisjonell strøm, og det feiler i to retninger samtidig. Hvert fragment betaler for en fil-lesing selv når siden allerede ligger i operativsystemets cache, og markøren er delt, muterbar tilstand. Derfor kunne ikke en lokal fil og byteområdekilden bak progressiv PDF-range-innlasting med forhåndshenting kjøre samme parserkode uten å slåss om posisjonen. PDFlibPas løser begge deler ved å gjøre lesing på absolutt offset fra en optimalisering til en kontrakt
Hva garanterer TPDFReadAtStream?
TPDFReadAtStream garanterer en lesing på et absolutt offset som verken avhenger av eller endrer den logiske strømmarkøren. Det er en abstrakt TStream-avleder med nøyaktig én virtuell metode, og begge markøruavhengige kilder i biblioteket arver fra den: TReadOnlyMappedFileStream for lokale filer og TByteRangeStream for fjernkilder som leveres med range-forespørsler. Objektleseren spør én gang om kilden er en TPDFReadAtStream og faller tilbake til den gamle sekvensen med seek og read når den ikke er det, så en vanlig filstrøm eller minnestrøm fortsetter å fungere uendret
type
// Skrivebeskyttede strømmer der absolutte lesinger unngår delt Seek pluss Read
TPDFReadAtStream = class(TStream)
public
function ReadAt(Offset: Int64; var Buffer;
Count: LongInt): LongInt; virtual; abstract;
end;
// Vindusbasert skrivebeskyttet tilgang til én lokal fil
TReadOnlyMappedFileStream = class(TPDFReadAtStream)
private
FMemoryMapped: Boolean;
public
constructor Create(const FileName: WideString; WindowSize: Int64 = 0);
function GetStats: TPDFMappedFileStats;
function ReadAt(Offset: Int64; var Buffer;
Count: LongInt): LongInt; override;
property MemoryMapped: Boolean read FMemoryMapped;
end;
Forskjellen betyr mer enn signaturen antyder. ReadAt bruker offset-et den får og lar Position være nøyaktig der den var, noe som gjør at nestede parsernivåer kan utstede lesinger uten en save-and-restore-runde rundt hvert kall. TReadOnlyMappedFileStream implementerer fortsatt Read, Seek og Size som enhver annen TStream, Seek begrenser den logiske posisjonen til filen, og Write returnerer alltid 0 fordi kilden åpnes skrivebeskyttet
Åpne en PDF gjennom en minnekartlagt visning i Delphi
To eksplisitte entry points åpner en minnekartlagt kilde, og ingen av dem endrer oppførselen til entry points du allerede bruker. LoadFromMappedFile laster inn og velger et dokument; DAOpenMappedFile returnerer et Direct Access-håndtak over den samme filen, som er modusen du vil bruke når du slår sammen og deler PDF-er på gigabyte-størrelse gjennom Direct Access. LoadFromFile og DAOpenFile beholder delings-, feil- og kompatibilitetssemantikken sin urørt, så ingenting flytter på seg for callers som ikke velger funksjonen. Begge de minnekartlagte entry points tar en ønsket WindowSize i byte og en Options-bitmaske, og begge aksepterer 0 for begge deler
var
Pdf: TPDFlib;
Payload: AnsiString;
Info: WideString;
begin
Pdf := TPDFlib.Create;
try
// WindowSize 0 velger standardverdien på 64 MiB; mapping er obligatorisk her
if Pdf.LoadFromMappedFile('archive-2026.pdf', '', 0,
PDF_MAPPED_FILE_REQUIRE_MAPPING) <> 1 then
raise Exception.CreateFmt('mapped open refused, LastErrorCode=%d',
[Pdf.LastErrorCode]);
// Utsatt uthenting går nå gjennom minnekartlagte vinduer i stedet for å seeke
Payload := Pdf.GetEmbeddedFileContentToString(1);
if Pdf.GetMappedFileInfo(Info) = 1 then
Writeln(Info);
finally
Pdf.Free;
end;
end;
Hva håndhever PDF_MAPPED_FILE_REQUIRE_MAPPING egentlig?
PDF_MAPPED_FILE_REQUIRE_MAPPING gjør en stille fallback om til en umiddelbar og diagnostiserbar feil ved åpning. Når Options står på 0, aksepterer begge entry points en skrivebeskyttet filstrøm-fallback: Hvis plattformen ikke har mapping-kode, eller mapping-kallet feiler, åpnes dokumentet fortsatt, og hver lesing går gjennom en vanlig filstrøm. Med flagget satt aksepterer PDFlibPas bare input når den første visningen er etablert, og rapporterer avvisningen gjennom LastErrorCode 401 i stedet for å laste et dokument som stille oppfører seg akkurat som den gamle stien
På Windows åpner den minnekartlagte strømmen et nytt skrivebeskyttet håndtak med FILE_SHARE_READ, FILE_SHARE_WRITE og FILE_SHARE_DELETE sammen med FILE_FLAG_RANDOM_ACCESS, oppretter en PAGE_READONLY-mapping over det og kartlegger det første vinduet inne i konstruktøren. Det å mappe ivrig er hele poenget: En feil på "mapping required" blir synlig ved LoadFromMappedFile, ikke ved den første late objektlesingen halvveis gjennom en renderingsjobb. Vær likevel tydelig på hvor garantien stopper. Mapping-koden kompileres bare for Windows-mål, og en fil på null byte forsøker aldri mapping, så PDF_MAPPED_FILE_REQUIRE_MAPPING er en forespørsel som legitimt kan feile, ikke et portabelt løfte. En negativ WindowSize, eller en bit i Options som ikke er den ene dokumenterte verdien, avvises direkte med den samme feilen 401
Ett vindu, mappet på nytt etter allokeringsgranulariteten
Det beholdes alltid bare én visning, og det er dette som holder bruken av adresseområdet uavhengig av filstørrelsen. En WindowSize på 0 velger 64 MiB; en verdi under systemets allokeringsgranularitet heves til den; en verdi over 1 GiB begrenses; og resultatet rundes opp til et helt antall granularitetsenheter, 65536 byte på Windows med mindre GetSystemInfo rapporterer en annen dwAllocationGranularity. Når en lesing havner utenfor den gjeldende visningen, avmapper PDFlibPas den, justerer det forespurte offset-et ned til en granularitetsgrense og mapper et nytt vindu der. Det siste vinduet begrenses til den fysiske filstørrelsen, så visningen strekker seg aldri forbi slutten av filen
Én lesing kan krysse et hvilket som helst antall vinduer: Løkken kopierer det den gjeldende visningen kan levere, mapper på nytt og fortsetter, og en forespørsel som løper forbi slutten, returnerer et kort antall i stedet for å feile. PDFlibPas gjør med hensikt ikke dette: å gi deg en peker inn i visningen, fordi den neste lesingen over en vindusgrense ugyldiggjør den, og ingen caller med rimelighet kunne beskytte seg mot det. Mappede byte kopieres rett inn i parser-eide målbuffer, noe som fjerner den ekstra fil-inputbufferen og posisjonsbyttingen, men biblioteket lover ikke nullkopiering av den endelige parserlagringen. Vinduslesing på lesesiden kan også kombineres med skrivesiden, siden bytebasert referanseforskyvning under en rask PDF-sammenslåing strømmer objektbyte ut mens den minnekartlagte kilden strømmer dem inn. Avveiingen for vindusstørrelse er åpenbar: Et mindre vindu bruker mindre adresseområde og mapper oftere, noe som vanligvis er riktig valg i en 32-biters prosess
Hva beskytter låsen, og hva rapporterer GetMappedFileInfo?
Én kritisk seksjon dekker den minnekartlagte visningen, fallback-filmarkøren, den logiske posisjonen og statistikken, og skillet mellom de to lesemetodene følger direkte av det. ReadAt tar låsen og kaller den låsefrie interne leseren; Read tar den samme låsen, kaller den samme interne leseren ved den gjeldende logiske posisjonen og flytter den deretter frem. Å bruke den interne funksjonen i stedet for den offentlige ReadAt er det som unngår rekursiv låsing, og å holde låsen gjennom hele kopieringsløkken er det som holder remapping av ett vindu korrekt under samtidige kall. En Free Pascal-detalj er verdt å kjenne før portering: FPC-Windows-enheten deklarerer sin egen record med navnet TCriticalSection, så feltet og opprettelsen må skrives som SyncObjs.TCriticalSection. Delphi kompilerer den ukvalifiserte formen uten problemer; FPC løser den til en record uten Create, Enter eller Leave
var
Pdf: TPDFlib;
Handle, PageRef: Integer;
Info: WideString;
begin
Pdf := TPDFlib.Create;
try
Handle := Pdf.DAOpenMappedFile('archive-2026.pdf', '',
16 * 1024 * 1024, PDF_MAPPED_FILE_REQUIRE_MAPPING);
if Handle = 0 then
Exit;
try
PageRef := Pdf.DAFindPage(Handle, 1);
Writeln(Pdf.DAExtractPageText(Handle, PageRef, 0));
// {"memoryMapped":true,"fileSize":...,"remapCount":...}
if Pdf.DAGetMappedFileInfo(Handle, Info) = 1 then
Writeln(Info);
finally
Pdf.DACloseFile(Handle);
end;
finally
Pdf.Free;
end;
end;
memoryMappeder false når den portable filstrøm-fallbacken er aktiv, og det er det ene feltet som beviser at en mapping aldri ble etablertwindowSizeer det effektive, justerte vinduet, ikke verdien du ba om, ogmappedByteser mindre enn det i halevinduetmappedOffseter den allokeringsjusterte starten på den beholdte visningen, eller -1 når ingen visning er aktivreadCallsteller vellykkede leseforspørsler innenfor området,bytesReadteller byte som er kopiert til callers, ogremapCountinkluderer den første visningen
Målrettede regresjoner dekker absolutte lesinger på tvers av vinduer, bevaring av den logiske markøren, korte lesinger ved halen, ugyldige offset-er, avviste skrivinger, remapping mellom adskilte vinduer, utsatt uthenting av et ukomprimerbart vedlegg på 220 KB og statistikk som blir ugyldig etter DACloseFile. De hodeløse Win32- og Win64-testpakkene oppdaget hver 1467 tester og besto alle uten ignorerte, feilede, feilende eller lekkede resultater. Hvis du arbeider med PDF-er på gigabyte-størrelse i Delphi eller C++Builder og profilereren stadig peker på fil-lesing i stedet for parsing, er de minnekartlagte entry points verdt en ettermiddags måling, og GetMappedFileInfo forteller deg om du faktisk fikk en mapping. Den fullstendige API-referansen og en prøveversjon finnes på siden for PDFlibPas PDF-bibliotek for Delphi