PDFium Component dokáže otvoriť PDF, ktorý žije vnútri väčšieho buffera, priamo z rozsahu bajtov. Preťaženie LoadDocument(const Data: TBytes; Index, Count: Integer; Buffered: Boolean) adresuje okno na mieste, takže nie je potrebný žiadny predbežný Copy. Výmenou vás žiada pochopiť jedno pravidlo: keď je Buffered False, podkladové pole je požičané, nie skopírované
Toto je odlišný mechanizmus od prístupu riadeného callbackom opísaného v streamovaní veľkých PDF na požiadanie s PDFium VCL, ktorý PDFium odovzdá čítač FPDF_FILEACCESS a necháva ho ťahať bloky z disku podľa potreby. Ten je pre dokumenty príliš veľké na to, aby sa zmestili do RAM. Tento je pre dokumenty už v RAM, sediace na známom offsete vnútri niečoho iného. Tie dva sa dopĺňajú, a posledná sekcia vysvetľuje, ktorá situácia patrí ktorej
40 MB kópia, o ktorú nikto nežiadal
Scenár sa objavuje všade, kde PDF cestujú vnútri iných formátov. Mailový sklad drží telá správ a prílohy v jednom zázname. Archívny kontajner zreťazuje manifest, pár obrázkov a PDF. Vlastný drôtový protokol rámuje dokument za hlavičkou s prefixom dĺžky. V každom prípade skončíte s jedným veľkým TBytes a vedomosťou, že PDF začína na bajte 1 182 336 a beží 312 kilobajtov
Predtým, než existovalo preťaženie rozsahu bajtov, idiomatickou odpoveďou bolo Copy(Data, Index, Count), ktoré alokuje druhé pole a memcpy-uje okno doň. Potom tento výrez odovzdáte LoadDocument s Buffered = True, čo ho skopíruje znova do súkromného buffera komponenty. Dve kópie tých istých bajtov, jedna z nich čistá ceremónia, a pri veľkom skenovaní schránky opakovaná pre každú správu. Preťaženie rozsahu bajtov odstraňuje prvú kópiu bezpodmienečne a druhú voliteľne
Čo preťaženie rozsahu bajtov skutočne robí
Preťaženie je zámerne tenké: validuje, vypočíta jeden ukazovateľ, a deleguje na ukazovateľovú formu LoadDocument, cez ktorú sa už zlieva celá rodina. Index je od nuly, Count je dĺžka v bajtoch, a Buffered je predvolene True, presne tak ako pri ostatných preťaženiach. Jednoargumentové LoadDocument(const Data: TBytes; Buffered: Boolean) je teraz samo osebe iba volaním tohto s Index = 0 a Count = Length(Data), takže existuje jedna validačná cesta namiesto dvoch
Jeho volanie vyzerá ako kód, ktorý ste už písali, mínus výrez
var
Frame: TBytes; // whole container record, tens of megabytes
Offset, Size: Integer;
begin
Frame := LoadContainerRecord('mailbox.dat');
LocateEmbeddedPdf(Frame, Offset, Size); // your container parser
// No Copy(Frame, Offset, Size) here - the window is addressed in place
Pdf.LoadDocument(Frame, Offset, Size, True);
try
RenderPreview(Pdf);
finally
Pdf.UnloadDocument;
end;
end;
Prečo Index plus Count pretečie kontrolu hraníc?
Pretože Index aj Count sú Integer, a súčet dvoch veľkých kladných hodnôt Integer nie je nutne veľký kladný Integer. Toto je technické jadro preťaženia, a je to jediné miesto, kde prirodzene vyzerajúca kontrola je diera v bezpečnosti pamäte. Zjavná formulácia je nesprávna
// WRONG: Index + Count is evaluated in Integer and can wrap negative
if Index + Count <= Length(Data) then
DataPtr := @Data[Index];
// RIGHT: reject signs first, then bound each term separately,
// with the only arithmetic done as a subtraction that cannot wrap
Check(Index >= 0, 'PDF byte range index cannot be negative');
Check(Count >= 0, 'PDF byte range count cannot be negative');
Check(Index <= Length(Data), 'PDF byte range index exceeds data length');
Check(Count <= Length(Data) - Index, 'PDF byte range exceeds data length');
Prejdite si zlyhávajúci prípad. Zoberte Index = 2000000000 a Count = 2000000000. Ich skutočný súčet je štyri miliardy, ale v 32-bitovej znamienkovej aritmetike výsledok pretečie na presne mínus 294 967 296. Táto hodnota je pohodlne menšia než Length(Data), takže nesprávna kontrola prejde, @Data[Index] sa vezme ďaleko mimo pole, a PDFium dostane divoký ukazovateľ plus dvojgigabajtovú dĺžku. Čo nasleduje, je v dobrý deň porušenie prístupu a v zlý deň tiché parsovanie nesúvisiacej pamäte procesu
Správne poradie to opravuje tak, že nikdy nesčíta. Záporné hodnoty sa odmietnu skôr, než je čokoľvek indexované, takže @Data[Index] sa nikdy nevezme pod pole. Potom je Index ohraničený sám osebe voči Length(Data), čo garantuje, že Length(Data) - Index je nezáporný Integer. Až potom sa Count porovná voči tomuto zvyšku. Každá medziľahlá hodnota zostáva vnútri reprezentovateľného rozsahu, takže žiadna build konfigurácia nemôže zmeniť výsledok. Nedajte sa zlákať spoliehať sa ani na kontrolu pretečenia {$Q+} ako záchrannú sieť: release buildy bežne dodávajú s ňou vypnutou, a aj keď je zapnutá, premenili ste chybu bezpečnosti pamäte na EIntOverflow unikajúcu zo stredu validačnej rutiny. PDFium Component zaobchádza s nedôveryhodnou aritmetikou dĺžky rovnako, ako zaobchádza so zvyškom hranice, disciplína pokrytá šíršie v spevňovaní PDFium VCL ABI a bezpečnosti pamäte v Delphi
Prečo musí okno s nulovou dĺžkou odovzdať nil?
Pretože @Data[Index] nie je legálny výraz pre každý Index, ktorý validácia akceptuje. Index = Length(Data) s Count = 0 je dokonale správne formované prázdne okno na chvoste buffera, a prázdne TBytes dáva Index = 0 na poli, ktoré vôbec nemá prvok nula. Vzatie adresy v ktoromkoľvek prípade indexuje za koniec, alebo dereferencuje nil dynamické pole. Takže preťaženie sa vetví: Count = 0 vyprodukuje nil ukazovateľ, akékoľvek iné počítanie vyprodukuje @Data[Index]. Nil potom tečie do ukazovateľového preťaženia, ktorého vlastná poistka akceptuje nil ukazovateľ, keď je veľkosť nula, a načítanie skončí obyčajnou chybou „Cannot load PDF document“ namiesto porušenia prístupu. Volajúci, ktorý vypočítal nulabajtové okno z chybne formovaného kontajnera, dostane čistú, zachytiteľnú EPdfError ako pri akomkoľvek inom zlom vstupe
Požičané alebo skopírované: čo rozhoduje Buffered
Buffered volí kontrakt vlastníctva, a je to jediný parameter tu, ktorý má dôsledky presahujúce samotné volanie. S Buffered = True PDFium Component skopíruje vybrané okno, a iba okno, do svojho interného buffera pred načítaním. 40 MB kontajner sa nekopíruje; 312 KB PDF sa kopíruje. Akonáhle LoadDocument vráti riadenie, môžete kontajner okamžite uvoľniť, znovu použiť alebo prepísať, pretože komponenta naň už neodkazuje. Toto je predvolené nastavenie a správna voľba pre takmer všetok kód
Buffered = False odovzdá @Data[Index] priamo do FPDF_LoadMemDocument64, a PDFium si tento ukazovateľ ponechá počas celej životnosti dokumentu namiesto kopírovania bajtov. To robí načítanie bez alokácie, a robí to celé podkladové TBytes požičaným zdrojom. Musí zostať živé a nezmenené, kým nebeží UnloadDocument alebo kým Active nejde na False. Nie okno, celé pole: dynamické pole je počítané referenciami ako jednotka, a uvoľnenie poslednej referencie kdekoľvek vo vašom kóde uvoľní pamäť, ktorú PDFium ešte číta. Nastavenie Length na ňom je rovnako fatálne, pretože realokácia môže presunúť blok. Uveďte toto vo vlastnej dokumentácii API všade, kde takéto načítanie sprístupňujete, v tom istom duchu ako akúkoľvek inú hranicu požičaj-verzus-vlastni v Pascal kóde; režim zlyhania je identický s hazardami aliasovania opísanými v úniku FillChar a výsledného reťazca v Delphi, kde buffer vyzerá vlastnený, ale nie je
type
TFrameSession = class
private
FFrame: TBytes; // owns the backing storage for as long as FPdf is loaded
FPdf: TPdf;
public
procedure OpenEmbedded(Offset, Size: Integer);
destructor Destroy; override;
end;
procedure TFrameSession.OpenEmbedded(Offset, Size: Integer);
begin
// Buffered = False: FFrame must outlive the loaded document
FPdf.LoadDocument(FFrame, Offset, Size, False);
end;
destructor TFrameSession.Destroy;
begin
FPdf.UnloadDocument; // release the borrow first
FFrame := nil; // only now may the storage go
inherited;
end;
Keď je okno rozsahu bajtov nesprávny nástroj
Buďte úprimní ohľadom hranice. Preťaženie rozsahu bajtov predpokladá, že kontajner je už úplne v pamäti, a Count je Integer, takže jedno okno nemôže presiahnuť dva gigabajty. Ak je kontajner 6 GB archív na disku, alebo prichádza cez socket, ktorý nemôžete pretočiť späť, toto preťaženie vám nemôže pomôcť a čítanie celej veci do TBytes len na adresovanie okna vnútri neho poráža celý zmysel. Presne tam patrí cesta FPDF_FILEACCESS, a článok o streamovaní na požiadanie ukazuje, ako sprístupniť pohľad na súbor posunutý o offset ako vlastný zdroj dokumentu. Rovnako, ak vložené bajty potrebujú transformáciu skôr, než ich PDFium uvidí, dekompresiu, dešifrovanie, krok rozbalenia, potom je skutočná kópia nevyhnutná a Buffered = True na transformovanom poli je úprimná odpoveď. Okno rozsahu bajtov sa oplatí presne v jednom tvare: súvislé, nezmenené PDF bajty, už rezidentné, na známom offsete
Ak toto zvažujete pre viewer, náhľadový panel alebo dávkovú intake pipeline, preťaženie rozsahu bajtov a streamovací loader sú dve zo stratégií načítania, ktoré PDFium Component dodáva popri načítaní zo súboru, streamu a surového ukazovateľa. Úplný povrch API, licencovanie a podpora verzií Delphi a C++Builder sú zdokumentované na stránke produktu PDFium Component