PDFium Component kan åbne en PDF, der lever inde i en større buffer, direkte fra et byte-interval. Overloadet LoadDocument(const Data: TBytes; Index, Count: Integer; Buffered: Boolean) adresserer et vindue på plads, så ingen indledende Copy er nødvendig. Til gengæld beder det dig forstå én regel: når Buffered er False, er det bagvedliggende array lånt, ikke kopieret
Dette er en anden mekanisme end den callback-drevne tilgang beskrevet i streaming af store PDF'er on-demand med PDFium VCL, som giver PDFium en FPDF_FILEACCESS-læser og lader den trække blokke fra disk efter behov. Den er til dokumenter for store til at holde i RAM. Denne er til dokumenter, der allerede er i RAM, siddende ved en kendt offset inde i noget andet. De to komplementerer hinanden, og sidste afsnit forklarer, hvilken situation der hører til hvilken
De 40 MB kopi, ingen bad om
Scenariet dukker op, hvor end PDF'er rejser inde i andre formater. En mailstore holder beskedkroppe og vedhæftninger i én post. En arkivcontainer sammenkæder et manifest, nogle billeder og en PDF. En brugerdefineret wire-protokol indrammer et dokument bag et længdeprefikset hoved. I hvert tilfælde ender man med at holde ét stort TBytes og vide, at PDF'en starter ved byte 1 182 336 og løber i 312 kilobytes
Før byte-interval-overloadet fandtes, var det idiomatiske svar Copy(Data, Index, Count), som allokerer et andet array og memcpy'er vinduet ind i det. Man afleverer det så til LoadDocument med Buffered = True, som kopierer det igen ind i komponentens private buffer. To kopier af de samme bytes, én af dem ren ceremoni, og på en stor mailboks-scanning gentaget for hver besked. Byte-interval-overloadet fjerner den første kopi ubetinget og den anden kopi valgfrit
Hvad byte-interval-overloadet egentlig gør
Overloadet er tyndt af design: det validerer, beregner én pointer og delegerer til pointer-formen af LoadDocument, som hele familien allerede kanaliserer gennem. Index er nul-baseret, Count er en bytelængde, og Buffered er som standard True nøjagtig som på de andre overloads. Enkelt-argument-LoadDocument(const Data: TBytes; Buffered: Boolean) er selv nu blot et kald til denne med Index = 0 og Count = Length(Data), så der er én valideringssti frem for to
At kalde den ligner den kode, man allerede skrev, minus udsnittet
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;
Hvorfor giver Index plus Count overløb i grænsetjekket?
Fordi Index og Count begge er Integer, og summen af to store positive Integer-værdier ikke nødvendigvis er en stor positiv Integer. Dette er den tekniske kerne af overloadet, og det er det ene sted, hvor et naturligt udseende tjek er et hukommelsessikkerhedshul. Den oplagte formulering er forkert
// 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');
Følg det fejlende tilfælde igennem. Tag Index = 2000000000 og Count = 2000000000. Deres reelle sum er fire milliarder, men i 32-bit signeret aritmetik ombukkes resultatet til nøjagtig minus 294 967 296. Den værdi er komfortabelt mindre end Length(Data), så det forkerte tjek består, @Data[Index] tages langt uden for arrayet, og PDFium overdrages en vild pointer plus en to-gigabyte-længde. Hvad der følger, er en access violation på en god dag og stiltiende parsing af urelateret proceshukommelse på en dårlig
Den korrekte rækkefølge retter dette ved aldrig at lægge sammen. Negative værdier afvises, før noget indekseres, så @Data[Index] aldrig kan tages under arrayet. Så begrænses Index alene mod Length(Data), hvilket garanterer, at Length(Data) - Index er en ikke-negativ Integer. Først derefter sammenlignes Count mod den rest. Hver mellemliggende værdi forbliver inden for det repræsenterbare interval, så ingen build-konfiguration kan ændre resultatet. Lad dig ikke friste til at stole på {$Q+}-overløbstjek som sikkerhedsnet heller: release-builds skibes rutinemæssigt med det slået fra, og selv når det er slået til, har man konverteret en hukommelsessikkerhedsfejl til en EIntOverflow, der flygter fra midten af en valideringsrutine. PDFium Component behandler upålidelig længdearitmetik på samme måde, den behandler resten af grænsen, en disciplin dækket bredere i hærdning af PDFium VCL ABI og hukommelsessikkerhed i Delphi
Hvorfor skal et nul-længde-vindue give nil?
Fordi @Data[Index] ikke er et lovligt udtryk for hvert Index, valideringen accepterer. Index = Length(Data) med Count = 0 er et helt velformet tomt vindue ved halen af bufferen, og en tom TBytes giver Index = 0 på et array, der slet ikke har noget element nul. At tage adressen i begge tilfælde indekserer forbi enden eller dereferencerer et nil dynamisk array. Så forgrener overloadet sig: Count = 0 giver en nil-pointer, enhver anden count giver @Data[Index]. Nil'en strømmer så ind i pointer-overloadet, hvis egen vagt accepterer en nil-pointer, når størrelsen er nul, og indlæsningen ender i den almindelige "Cannot load PDF document"-fejl frem for en access violation. En kalder, der beregnede et nul-byte-vindue fra en misdannet container, får en ren, opfangelig EPdfError som enhver anden dårlig input
Lånt eller kopieret: hvad Buffered afgør
Buffered vælger ejerskabskontrakten, og det er den eneste parameter her med konsekvenser ud over kaldet. Med Buffered = True kopierer PDFium Component det valgte vindue, og kun vinduet, ind i sin interne buffer, før den indlæser. De 40 MB-containeren kopieres ikke; de 312 KB PDF er. Så snart LoadDocument returnerer, kan man frigive, genbruge eller overskrive containeren straks, fordi komponenten ikke længere refererer til den. Dette er standarden og det rigtige valg for næsten al kode
Buffered = False sender @Data[Index] direkte til FPDF_LoadMemDocument64, og PDFium beholder den pointer for dokumentets levetid frem for at kopiere bytene. Det gør indlæsningen allokeringsfri, og det gør hele det bagvedliggende TBytes til en lånt ressource. Det skal forblive levende og uændret, indtil UnloadDocument kører, eller Active bliver False. Ikke vinduet, hele arrayet: et dynamisk array er reference-tællet som en enhed, og at lade den sidste reference gå et sted i ens kode frigiver den hukommelse, PDFium stadig læser. At sætte Length på det er lige så fatalt, fordi en reallokering kan flytte blokken. Skriv dette ind i din egen API-dokumentation, hvor du eksponerer en sådan indlæsning, i samme ånd som enhver anden lån-versus-eje-grænse i Pascal-kode; fejltilstanden er identisk med de aliasing-farer, der er beskrevet i FillChar og result-streng-lækket i Delphi, hvor en buffer ser ejet ud og ikke er det
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;
Når byte-interval-vinduet er det forkerte værktøj
Vær ærlig om grænsen. Byte-interval-overloadet antager, at containeren allerede er fuldt i hukommelsen, og Count er en Integer, så ét vindue kan ikke overstige to gigabyte. Er containeren et 6 GB-arkiv på disk, eller ankommer over en socket, man ikke kan spole tilbage, kan dette overload ikke hjælpe, og at læse det hele ind i TBytes bare for at adressere et vindue indeni det besejrer pointen. Det er præcis der, FPDF_FILEACCESS-stien hører til, og on-demand-streaming-artiklen viser, hvordan man eksponerer en offset-forskudt visning af en fil som en brugerdefineret dokumentkilde. Tilsvarende, hvis de indlejrede bytes behøver transformation, før PDFium ser dem, dekomprimering, dekryptering, et unwrapping-trin, så er en reel kopi uundgåelig, og Buffered = True på det transformerede array er det ærlige svar. Byte-interval-vinduet betaler sig i præcis én form: sammenhængende, uændrede PDF-bytes, allerede residente, ved en kendt offset
Evaluerer man dette til en viewer, en preview-rude eller en batch-intake-pipeline, er byte-interval-overloadet og den strømmende loader to af de indlæsningsstrategier, PDFium Component tilbyder sammen med fil-, strøm- og rå pointer-indlæsninger. Den fulde API-flade, licensering og understøttede Delphi- og C++Builder-versioner er dokumenteret på PDFium Component-produktsiden