PDFium Component može otvoriti PDF koji živi unutar većeg međuspremnika izravno iz raspona bajtova. Preopterećenje LoadDocument(const Data: TBytes; Index, Count: Integer; Buffered: Boolean) adresira prozor na mjestu, tako da prethodni Copy nije potreban. Zauzvrat traži da razumijete jedno pravilo: kad je Buffered False, podložni niz je posuđen, ne kopiran
Ovo je drugačiji mehanizam od pristupa vođenog povratnim pozivom opisanog u streamingu velikih PDF-ova na zahtjev s PDFium VCL, koji PDFium-u predaje čitač FPDF_FILEACCESS i pušta ga da povlači blokove s diska kako mu trebaju. To je za dokumente prevelike da stanu u RAM. Ovo je za dokumente koji su već u RAM-u, na poznatom offsetu unutar nečeg drugog. Dva su komplementi, a posljednji odjeljak objašnjava koja situacija pripada kojem
Kopija od 40 MB koju nitko nije tražio
Scenarij se pojavljuje gdje god PDF-ovi putuju unutar drugih formata. Pohrana pošte drži tijela poruka i privitke u jednom zapisu. Kontejner arhive spaja manifest, nekoliko slika i PDF. Prilagođeni žični protokol uokviruje dokument iza zaglavlja s prefiksom duljine. U svakom slučaju završite s jednim velikim TBytes i znanjem da PDF počinje na bajtu 1.182.336 i traje 312 kilobajta
Prije nego je preopterećenje raspona bajtova postojalo, idiomatski odgovor bio je Copy(Data, Index, Count), koji alocira drugi niz i memcpy-ja prozor u njega. Zatim taj isječak predajete LoadDocument s Buffered = True, koji ga ponovno kopira u privatni međuspremnik komponente. Dvije kopije istih bajtova, jedna od njih čista ceremonija, i na velikom skeniranju poštanskog sandučića ponovljena za svaku poruku. Preopterećenje raspona bajtova uklanja prvu kopiju bezuvjetno i drugu neobvezno
Što preopterećenje raspona bajtova zapravo radi
Preopterećenje je namjerno tanko: provjerava, izračunava jedan pokazivač, i delegira na oblik pokazivača LoadDocument kroz koji već prolazi cijela obitelj. Index je baziran na nuli, Count je duljina u bajtovima, a Buffered zadano je True točno kao i na ostalim preopterećenjima. Jednoargumentni LoadDocument(const Data: TBytes; Buffered: Boolean) sam je sad samo poziv ovome s Index = 0 i Count = Length(Data), tako da postoji jedan put provjere umjesto dva
Pozivanje izgleda poput koda koji ste već pisali, minus isječak
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;
Zašto Index plus Count prelijeva provjeru granica?
Zato što su Index i Count oba Integer, a zbroj dvije velike pozitivne Integer vrijednosti nije nužno velik pozitivan Integer. Ovo je tehnička srž preopterećenja, i jedino mjesto gdje prirodno izgledajuća provjera je rupa u sigurnosti memorije. Očita formulacija je pogrešna
// 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');
Provedite kvarni slučaj kroz to. Uzmite Index = 2000000000 i Count = 2000000000. Njihov je stvarni zbroj četiri milijarde, ali u 32-bitnoj potpisanoj aritmetici rezultat se omota u točno minus 294.967.296. Ta je vrijednost udobno manja od Length(Data), pa pogrešna provjera prolazi, @Data[Index] se uzima daleko izvan niza, a PDFium-u se predaje divlji pokazivač plus duljina od dva gigabajta. Ono što slijedi je narušavanje pristupa u dobrom danu i tiho raščlanjivanje nepovezane memorije procesa u lošem
Ispravan redoslijed ovo popravlja nikad ne zbrajajući. Negativne vrijednosti odbijaju se prije nego se bilo što indeksira, tako da se @Data[Index] nikad ne može uzeti ispod niza. Zatim se Index ograničava samostalno protiv Length(Data), što jamči da je Length(Data) - Index nenegativan Integer. Tek se onda Count uspoređuje s tim ostatkom. Svaka posredna vrijednost ostaje unutar predstavljivog raspona, tako da nijedna konfiguracija builda ne može promijeniti ishod. Ne dajte se namamiti na oslanjanje na provjeru prelijevanja {$Q+} kao mrežu sigurnosti niti: release buildovi rutinski se isporučuju s njom isključenom, a čak i kad je uključena, pretvorili ste grešku sigurnosti memorije u EIntOverflow koji izlazi iz sredine rutine provjere. PDFium Component tretira nepouzdanu aritmetiku duljine na isti način na koji tretira ostatak granice, disciplina pokrivena šire u jačanju PDFium VCL ABI-ja i sigurnosti memorije u Delphiju
Zašto prozor nulte duljine mora proslijediti nil?
Zato što @Data[Index] nije legalan izraz za svaki Index koji provjera prihvaća. Index = Length(Data) s Count = 0 savršeno je dobro oblikovan prazan prozor na repu međuspremnika, a prazan TBytes daje Index = 0 na nizu koji uopće nema element nula. Uzimanje adrese u bilo kojem slučaju indeksira iza kraja, ili dereferencira nil dinamički niz. Pa se preopterećenje grana: Count = 0 daje nil pokazivač, svaki drugi count daje @Data[Index]. Nil zatim teče u preopterećenje pokazivača, čija vlastita zaštita prihvaća nil pokazivač kad je veličina nula, a učitavanje završava u običnoj grešci "Cannot load PDF document" umjesto narušavanja pristupa. Pozivatelj koji je izračunao prozor od nula bajtova iz neispravnog kontejnera dobiva čist, uhvatljiv EPdfError poput svakog drugog lošeg ulaza
Posuđeno ili kopirano: što Buffered odlučuje
Buffered bira ugovor vlasništva, i jedini je parametar ovdje s posljedicama izvan poziva. S Buffered = True, PDFium Component kopira odabrani prozor, i samo prozor, u svoj interni međuspremnik prije učitavanja. Kontejner od 40 MB se ne kopira; PDF od 312 KB se kopira. Čim se LoadDocument vrati, možete odmah osloboditi, ponovno koristiti ili prepisati kontejner, jer se komponenta više ne referencira na njega. Ovo je zadano i ispravan izbor za gotovo sav kod
Buffered = False prosljeđuje @Data[Index] izravno u FPDF_LoadMemDocument64, a PDFium zadržava taj pokazivač tijekom cijelog života dokumenta umjesto kopiranja bajtova. To čini učitavanje bez alokacije, i čini cijeli podložni TBytes posuđenim resursom. Mora ostati živ i nepromijenjen dok se UnloadDocument ne pokrene ili Active ne postane False. Ne prozor, cijeli niz: dinamički niz broji reference kao jedinica, a puštanje posljednje reference bilo gdje u vašem kodu oslobađa memoriju koju PDFium još čita. Postavljanje Length na njemu jednako je kobno, jer realokacija može premjestiti blok. Navedite ovo u vlastitoj API dokumentaciji gdje god izlažete takvo učitavanje, u istom duhu kao i svaka druga granica posuđivanja-nasuprot-posjedovanja u Pascal kodu; način neuspjeha identičan je opasnostima aliasinga opisanim u FillChar i curenju result stringa u Delphiju, gdje međuspremnik izgleda posjedovan, a nije
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;
Kad je prozor raspona bajtova pogrešan alat
Budite iskreni oko granice. Preopterećenje raspona bajtova pretpostavlja da je kontejner već posve u memoriji, a Count je Integer, pa jedan prozor ne može premašiti dva gigabajta. Ako je kontejner arhiva od 6 GB na disku, ili stiže preko socketa koji ne možete premotati, ovo preopterećenje vam ne može pomoći, a čitanje cijele stvari u TBytes samo da biste adresirali prozor unutar nje poražava poantu. To je upravo gdje pripada put FPDF_FILEACCESS, a članak o streamingu na zahtjev pokazuje kako izložiti pomaknuti prikaz datoteke kao prilagođeni izvor dokumenta. Jednako tako, ako ugrađeni bajtovi trebaju transformaciju prije nego ih PDFium vidi, dekompresiju, dešifriranje, korak raspakiranja, onda je prava kopija neizbježna, a Buffered = True na transformiranom nizu je iskren odgovor. Prozor raspona bajtova isplati se u točno jednom obliku: susjedni, nepromijenjeni PDF bajtovi, već rezidentni, na poznatom offsetu
Ako ovo procjenjujete za preglednik, panel pregleda ili pipeline paketnog unosa, preopterećenje raspona bajtova i streaming učitavač dvije su od strategija učitavanja koje PDFium Component isporučuje uz učitavanje iz datoteke, streama i sirovog pokazivača. Puna površina API-ja, licenciranje i podrška verzija Delphi i C++Builder dokumentirani su na stranici PDFium Component proizvoda