PDFium Component može otvoriti PDF koji živi unutar većeg bafera direktno iz opsega bajtova. Preopterećenje LoadDocument(const Data: TBytes; Index, Count: Integer; Buffered: Boolean) adresira prozor na licu mesta, tako da nikakav preliminaran Copy nije potreban. Zauzvrat traži da razumete jedno pravilo: kad je Buffered False, osnovni niz je pozajmljen, ne kopiran
Ovo je drugačiji mehanizam od pristupa vođenog callback-om opisanog u streaming velikih PDF-ova na zahtev sa PDFium VCL, koji daje PDFium-u čitač FPDF_FILEACCESS i pušta ga da povlači blokove sa diska po potrebi. Onaj je za dokumente prevelike da stanu u RAM. Ovaj je za dokumente koji su već u RAM-u, koji sede na poznatom offsetu unutar nečeg drugog. Njih dvoje se dopunjuju, a poslednji odeljak objašnjava koja situacija pripada kojoj
Kopija od 40 MB koju niko nije tražio
Scenario se pojavljuje gde god PDF-ovi putuju unutar drugih formata. Skladište pošte drži tela poruka i priloge u jednom zapisu. Kontejner arhive spaja manifest, nekoliko slika i PDF. Prilagođen žični protokol uokviruje dokument iza zaglavlja sa prefiksom dužine. U svakom slučaju završite sa jednim velikim TBytes i saznanjem da PDF počinje na bajtu 1.182.336 i traje 312 kilobajta
Pre nego što je preopterećenje opsega bajtova postojalo, idiomatski odgovor je bio Copy(Data, Index, Count), koji alocira drugi niz i memcpy-uje prozor u njega. Zatim tu isečku predajete LoadDocument sa Buffered = True, koji je ponovo kopira u privatan bafer komponente. Dve kopije istih bajtova, jedna od njih čist ceremonijal, i na velikom skeniranju poštanskog sandučeta ponavlja se za svaku poruku. Preopterećenje opsega bajtova uklanja prvu kopiju bezuslovno, a drugu opciono
Šta preopterećenje opsega bajtova zapravo radi
Preopterećenje je namerno tanko: validira, izračunava jedan pokazivač, i delegira formi pokazivača LoadDocument kroz koju se cela porodica već sliva. Index je zasnovan na nuli, Count je dužina bajtova, a Buffered je podrazumevano True tačno kao na ostalim preopterećenjima. Jednoargumentni LoadDocument(const Data: TBytes; Buffered: Boolean) je sam sada samo poziv ovom sa Index = 0 i Count = Length(Data), tako da postoji jedna putanja validacije umesto dve
Poziv izgleda kao kod koji ste već pisali, minus iseč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 prekoračuje proveru granica?
Zato što su i Index i Count oba Integer, a zbir dve velike pozitivne Integer vrednosti nije nužno velik pozitivan Integer. Ovo je tehničko jezgro preopterećenja, i jedino mesto gde provera koja prirodno izgleda ispravno je rupa u bezbednosti memorije. Očigledna 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');
Odradite slučaj neuspeha korak po korak. Uzmite Index = 2000000000 i Count = 2000000000. Njihov pravi zbir je četiri milijarde, ali u 32-bitnoj potpisanoj aritmetici rezultat se prelomi tačno na minus 294.967.296. Ta vrednost je udobno manja od Length(Data), tako da pogrešna provera prolazi, @Data[Index] se uzima daleko van niza, a PDFium-u se predaje divlji pokazivač plus dužina od dva gigabajta. Ono što sledi je access violation u dobrom danu, i tiho parsiranje nepovezane memorije procesa u lošem
Ispravan redosled to popravlja tako što nikad ne sabira. Negativne vrednosti se odbacuju pre nego što se bilo šta indeksira, tako da se @Data[Index] nikad ne može uzeti ispod niza. Zatim se Index ograničava sam za sebe naspram Length(Data), što garantuje da je Length(Data) - Index nenegativan Integer. Tek onda se Count poredi naspram tog ostatka. Svaka međuvrednost ostaje unutar reprezentabilnog opsega, tako da nijedna konfiguracija build-a ne može promeniti ishod. Ne dajte se u iskušenje da se oslonite na proveru prekoračenja {$Q+} kao mrežu bezbednosti: release build-ovi se rutinski isporučuju sa njom isključenom, a čak i kad je uključena, pretvorili ste bag bezbednosti memorije u EIntOverflow koji beži iz sredine rutine validacije. PDFium Component tretira nepouzdanu aritmetiku dužine na isti način na koji tretira ostatak granice, disciplina pokrivena šire u otvrdnjavanju PDFium VCL ABI i bezbednosti memorije u Delphi
Zašto prozor nulte dužine mora predati nil?
Zato što @Data[Index] nije legalan izraz za svaki Index koji validacija prihvata. Index = Length(Data) sa Count = 0 je savršeno dobro formiran prazan prozor na kraju bafera, a prazan TBytes daje Index = 0 na nizu koji uopšte nema element nula. Uzimanje adrese u bilo kom slučaju indeksira van kraja, ili dereferencira nil dinamički niz. Tako da se preopterećenje grana: Count = 0 daje nil pokazivač, bilo koji drugi count daje @Data[Index]. Nil tada teče u preopterećenje pokazivača, čija sopstvena straža prihvata nil pokazivač kad je veličina nula, i učitavanje se završava u običnoj grešci "Cannot load PDF document" umesto access violation-a. Pozivalac koji je izračunao prozor od nula bajtova iz deformisanog kontejnera dobija čist, hvatljiv EPdfError kao bilo koji drugi loš ulaz
Pozajmljeno ili kopirano: šta Buffered odlučuje
Buffered bira ugovor vlasništva, i to je jedini parametar ovde sa posledicama van poziva. Sa Buffered = True, PDFium Component kopira izabrani prozor, i samo prozor, u svoj interni bafer pre učitavanja. Kontejner od 40 MB se ne kopira; PDF od 312 KB se kopira. Čim se LoadDocument vrati, možete odmah osloboditi, ponovo koristiti ili prepisati kontejner, jer komponenta ga više ne referencira. Ovo je podrazumevano i ispravan izbor za skoro sav kod
Buffered = False prosleđuje @Data[Index] direktno FPDF_LoadMemDocument64, a PDFium drži taj pokazivač za život dokumenta umesto da kopira bajtove. To čini učitavanje bez alokacije, i čini čitav osnovni TBytes pozajmljenim resursom. Mora ostati živ i nepromenjen dok UnloadDocument ne pokrene ili Active ne postane False. Ne prozor, ceo niz: dinamički niz je referentno brojan kao jedinica, a puštanje poslednje reference bilo gde u vašem kodu oslobađa memoriju koju PDFium i dalje čita. Postavljanje Length na njemu je jednako fatalno, jer realokacija može pomeriti blok. Navedite ovo u sopstvenoj API dokumentaciji gde god izlažete takvo učitavanje, u istom duhu kao bilo koja druga granica pozajmi-naspram-poseduj u Pascal kodu; režim otkaza je identičan opasnostima aliasing-a opisanim u FillChar i curenju result string-a u Delphi, gde bafer izgleda posedovan, 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 opsega bajtova pogrešan alat
Budite iskreni oko granice. Preopterećenje opsega bajtova pretpostavlja da je kontejner već u potpunosti u memoriji, a Count je Integer, tako da jedan prozor ne može prekoračiti dva gigabajta. Ako je kontejner arhiva od 6 GB na disku, ili stiže preko soketa koji ne možete premotati, ovo preopterećenje vam ne može pomoći, a čitanje celog toga u TBytes samo da biste adresirali prozor unutar njega poražava poentu. To je upravo gde pripada putanja FPDF_FILEACCESS, a članak o streaming-u na zahtev pokazuje kako izložiti pomeren pogled fajla kao prilagođen izvor dokumenta. Jednako, ako ugrađeni bajtovi trebaju transformaciju pre nego što ih PDFium vidi, dekompresiju, dešifrovanje, korak raspakivanja, onda je prava kopija neizbežna i Buffered = True na transformisanom nizu je iskren odgovor. Prozor opsega bajtova se isplati u tačno jednom obliku: susedni, nepromenjeni PDF bajtovi, već rezidentni, na poznatom offsetu
Ako ovo procenjujete za pregledač, panel pregleda ili pipeline unosa serijskih poslova, preopterećenje opsega bajtova i streaming loader su dve od strategija učitavanja koje PDFium Component isporučuje pored učitavanja iz fajla, toka i sirovog pokazivača. Kompletna površina API-ja, licenciranje i podrška Delphi i C++Builder verzija dokumentovani su na stranici proizvoda PDFium Component