PDFlibPas dokáže otvoriť lokálne PDF cez ohraničený pamäťovo mapovaný pohľad iba na čítanie: LoadFromMappedFile a DAOpenMappedFile udržiavajú nad súborom presne jedno posuvné okno, podľa potreby ho mapujú nanovo a každý výrez objektu obslúžia čítaniami z absolútneho offsetu. Delphi PDF library nikdy nedrží celý zdroj v pamäti, takže využitie adresného priestoru zostáva pri raste súboru konštantné. Návrh je určený pre jednu záťaž: gigabajtové PDF, pri ktorých parser dokončil načítanie a stále sa vracia na disk, objekt po objekte a fragment streamu po fragmente streamu
Prečo zostávajú riedke čítania drahé aj po načítaní PDF?
Načítanie PDF neznamená, že je prečítané celé, a pri viacgigabajtovom súbore sa práve v tejto medzere stráca čas. Cross-reference tabuľka alebo cross-reference stream (ISO 32000-1 §7.5.4 a §7.5.8) zaznamenáva iba to, kde sa začína každý nepriamy objekt. Bajty prídu neskôr, keď sa renderuje stránka, dekóduje fontový program alebo extrahuje vložený file stream (ISO 32000-1 §7.11.4). Archív s veľkosťou 2 GB a desaťtisícmi objektov sa zmení na desaťtisíce malých neusporiadaných čítaní a v čase načítania nie je známe ani jedno z nich
Cesta, ktorou tieto čítania kedysi prechádzali, bolo spoločné Seek nasledované Read na jednom pozičnom streame, a zlyháva v dvoch smeroch naraz. Každý fragment zaplatí za čítanie súboru aj vtedy, keď stránka už býva v cache operačného systému, a kurzor je zdieľaný meniteľný stav, takže lokálny súbor a zdroj byte-range za progresívnym načítavaním rozsahov PDF s prefetchom nemohli používať rovnaký parser kód bez vzájomného zápasu o pozíciu. PDFlibPas rieši oboje tým, že čítanie z absolútneho offsetu povyšuje z optimalizácie na kontrakt
Čo garantuje TPDFReadAtStream?
TPDFReadAtStream garantuje čítanie z absolútneho offsetu, ktoré nezávisí od logického kurzora streamu ani ho nemení. Je to abstraktný potomok TStream s presne jednou virtuálnou metódou a oba zdroje v knižnici nezávislé od kurzora z neho dedia: TReadOnlyMappedFileStream pre lokálne súbory a TByteRangeStream pre vzdialené zdroje poskytované po rozsahoch. Čítačka výrezov objektov sa raz opýta, či jej zdroj je TPDFReadAtStream, a keď nie je, vráti sa k starému sledu seek-then-read, takže obyčajný file stream alebo memory stream funguje ďalej bez zmeny
type
// Streamy iba na čítanie, ktorých absolútne čítania obchádzajú spoločné Seek a Read
TPDFReadAtStream = class(TStream)
public
function ReadAt(Offset: Int64; var Buffer;
Count: LongInt): LongInt; virtual; abstract;
end;
// Prístup iba na čítanie k jednému lokálnemu súboru cez okná
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;
Rozdiel je dôležitejší, než naznačuje signatúra. ReadAt použije odovzdaný offset a nechá Position presne tam, kde bol, čo umožňuje vnoreným úrovniam parsera vydávať čítania bez tanca save-and-restore okolo každého volania. TReadOnlyMappedFileStream stále implementuje Read, Seek a Size ako každý iný TStream, Seek ohraničí logickú pozíciu v rámci súboru a Write vždy vráti 0, pretože zdroj je otvorený iba na čítanie
Otvorenie PDF cez mapovaný pohľad v Delphi
Mapovaný zdroj otvárajú dva explicitné vstupné body a ani jeden nemení správanie vstupných bodov, ktoré už používate. LoadFromMappedFile načíta a vyberie dokument; DAOpenMappedFile vráti Direct Access handle nad tým istým súborom, čo je režim, ktorý chcete pri spájaní a rozdeľovaní gigabajtových PDF cez Direct Access. LoadFromFile a DAOpenFile si zachovávajú svoju sémantiku zdieľania súborov, chýb a kompatibility, takže volajúcim, ktorí sa neprihlásia, sa nič neposunie. Oba mapované vstupné body prijímajú požadovaný WindowSize v bajtoch a bitovú masku Options a pri oboch je možné zadať 0 pre ktorúkoľvek z nich
var
Pdf: TPDFlib;
Payload: AnsiString;
Info: WideString;
begin
Pdf := TPDFlib.Create;
try
// WindowSize 0 vyberie predvolené okno 64 MiB; mapovanie je tu povinné
if Pdf.LoadFromMappedFile('archive-2026.pdf', '', 0,
PDF_MAPPED_FILE_REQUIRE_MAPPING) <> 1 then
raise Exception.CreateFmt('mapped open refused, LastErrorCode=%d',
[Pdf.LastErrorCode]);
// Odložená extrakcia teraz prechádza mapovanými oknami namiesto seekovania
Payload := Pdf.GetEmbeddedFileContentToString(1);
if Pdf.GetMappedFileInfo(Info) = 1 then
Writeln(Info);
finally
Pdf.Free;
end;
end;
Čo vlastne vynucuje PDF_MAPPED_FILE_REQUIRE_MAPPING?
PDF_MAPPED_FILE_REQUIRE_MAPPING mení tichý fallback na okamžité a diagnostikovateľné zlyhanie pri otváraní. Keď Options zostane na 0, oba vstupné body prijmú fallback na file stream iba na čítanie: ak platforma nemá kód pre mapovanie alebo volanie mapovania zlyhá, dokument sa aj tak otvorí a každé čítanie pôjde cez normálny file stream. Pri nastavenom príznaku PDFlibPas prijme vstup iba vtedy, keď sa vytvoril prvý pohľad, a odmietnutie nahlási cez LastErrorCode 401 namiesto načítania dokumentu, ktorý potichu funguje presne ako stará cesta
Vo Windows mapovaný stream otvorí druhý handle iba na čítanie s FILE_SHARE_READ, FILE_SHARE_WRITE a FILE_SHARE_DELETE spolu s FILE_FLAG_RANDOM_ACCESS, vytvorí nad ním mapovanie PAGE_READONLY a prvé okno namapuje už v konštruktore. Eager mapovanie je celý zmysel: zlyhanie „mapping required“ sa prejaví v LoadFromMappedFile, nie pri prvom lenivo odloženom čítaní objektu uprostred renderovacej úlohy. Treba si však presne uvedomiť, kde sa garancia končí. Kód mapovania sa kompiluje iba pre ciele Windows a súbor s nulovou veľkosťou sa o mapovanie vôbec nepokúsi, takže PDF_MAPPED_FILE_REQUIRE_MAPPING je požiadavka, ktorá môže legitímne zlyhať, nie prenositeľný sľub. Záporný WindowSize alebo ľubovoľný bit v Options okrem jedinej zdokumentovanej hodnoty sa odmietne priamo s rovnakou chybou 401
Jedno okno, nanovo mapované na alokačnú granularitu
Uchováva sa vždy iba jeden pohľad, a práve to udržiava využitie adresného priestoru nezávislé od veľkosti súboru. WindowSize 0 vyberie 64 MiB; hodnota menšia než systémová alokačná granularita sa na ňu zvýši; hodnota väčšia než 1 GiB sa ohraničí; výsledok sa zaokrúhli nahor na celý počet jednotiek granularity, vo Windows na 65536 bajtov, ak GetSystemInfo nenahlási inú hodnotu dwAllocationGranularity. Keď čítanie dopadne mimo aktuálneho pohľadu, PDFlibPas ho odmapuje, zarovná požadovaný offset nadol na hranicu granularity a na tomto mieste namapuje čerstvé okno. Posledné okno sa ohraničí fyzickou veľkosťou súboru, takže pohľad nikdy nepresiahne jeho koniec
Jedno čítanie môže prekročiť ľubovoľný počet okien: cyklus skopíruje všetko, čo dokáže dodať aktuálny pohľad, zmapuje ďalšie okno a pokračuje; požiadavka idúca za koniec vráti skrátený počet namiesto zlyhania. PDFlibPas zámerne nerobí to, že by vám odovzdal ukazovateľ do pohľadu, pretože ďalšie čítanie cez hranicu okna ho zneplatní a žiadny volajúci by sa proti tomu rozumne neubránil. Mapované bajty sa kopírujú priamo do cieľových bufferov vlastnených parserom, čím odpadá ďalší vstupný buffer súboru aj prepínanie pozície, knižnica však netvrdí, že finálne úložisko parsera je zero-copy. Windowing na strane čítania sa skladá aj so zápisom, pretože posun referencií na úrovni bajtov počas rýchleho spájania PDF streamuje bajty objektu von, zatiaľ čo mapovaný zdroj ich streamuje dnu. Trade-off veľkosti okna je očividný: menšie okno zaberie menej adresného priestoru a častejšie sa mapuje nanovo, čo je v 32-bitovom procese zvyčajne správna voľba
Čo chráni zámok a čo hlási GetMappedFileInfo
Jedna critical section pokrýva mapovaný pohľad, kurzor fallbackového súboru, logickú pozíciu aj štatistiky a rozdelenie medzi oboma metódami čítania z toho priamo vyplýva. ReadAt vezme zámok a zavolá internú čítačku bez zámku; Read vezme ten istý zámok, zavolá tú istú internú čítačku na aktuálnej logickej pozícii a potom ju posunie. Opätovné použitie internej funkcie namiesto verejného ReadAt zabraňuje rekurzívnemu zamykaniu a držanie zámku počas celej kopírovacej slučky udržiava správnosť remapovania jedného okna pri súbežných volaniach. Pred portovaním sa oplatí poznať jeden detail Free Pascalu: unit FPC Windows deklaruje vlastný record s názvom TCriticalSection, takže pole aj jeho vytvorenie musia byť zapísané ako SyncObjs.TCriticalSection. Delphi neúplnú formu bez problémov skompiluje, FPC ju vyrieši na record bez Create, Enter alebo 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;
memoryMappedje false vždy, keď je aktívny prenositeľný fallback na file stream, a je to jediné pole dokazujúce, že mapovanie sa nikdy nevytvorilowindowSizeje efektívne zarovnané okno, nie hodnota, ktorú ste požadovali, amappedBytesje v koncovom okne menšie než onomappedOffsetje na alokáciu zarovnaný začiatok uchovávaného pohľadu alebo -1, keď momentálne nie je aktívny žiadny pohľadreadCallspočíta úspešné požiadavky na čítanie v rámci rozsahu,bytesReadpočíta bajty skopírované volajúcim aremapCountzahŕňa aj počiatočný pohľad
Cielené regresie pokrývajú absolútne čítania cez hranicu okna, zachovanie logického kurzora, krátke čítania na konci, neplatné offsety, odmietnuté zápisy, remapovanie medzi oddelenými oknami, odloženú extrakciu 220 KB nekomprimovateľnej prílohy a neplatnosť štatistík po DACloseFile; headless suite Win32 aj Win64 objavila po 1467 testov a všetky prešli bez ignorovaných, zlyhaných, chybných či uniknutých výsledkov. Ak v Delphi alebo C++Builderi pracujete s gigabajtovými PDF a profiler stále ukazuje skôr na čítania súboru než na parsing, mapované vstupné body stoja za popoludnie merania a GetMappedFileInfo vám povie, či ste naozaj dostali mapovanie. Úplná referencia API a trial build sú na stránke PDFlibPas Delphi PDF library