PDFium Component può aprire un PDF che vive dentro un buffer più grande direttamente da un intervallo di byte. L'overload LoadDocument(const Data: TBytes; Index, Count: Integer; Buffered: Boolean) indirizza una finestra sul posto, così non serve alcun Copy preliminare. In cambio ti chiede di capire una regola: quando Buffered è False, l'array sottostante viene preso in prestito, non copiato
Questo è un meccanismo diverso dall'approccio guidato da callback descritto in streaming di PDF di grandi dimensioni on demand con PDFium VCL, che consegna a PDFium un reader FPDF_FILEACCESS e lo lascia prelevare blocchi dal disco man mano che ne ha bisogno. Quello serve per documenti troppo grandi per stare in RAM. Questo serve per documenti già in RAM, seduti a un offset noto dentro qualcos'altro. I due sono complementari, e l'ultima sezione spiega quale situazione appartiene a quale
La copia da 40 MB che nessuno ha chiesto
Lo scenario compare ovunque i PDF viaggino dentro altri formati. Un archivio di posta mantiene corpi dei messaggi e allegati in un unico record. Un contenitore di archivio concatena un manifest, alcune immagini e un PDF. Un protocollo di rete personalizzato incornicia un documento dietro un header con prefisso di lunghezza. In ogni caso finisci per tenere un unico grande TBytes e sapere che il PDF inizia al byte 1.182.336 e corre per 312 kilobyte
Prima che esistesse l'overload per byte range, la risposta idiomatica era Copy(Data, Index, Count), che alloca un secondo array e fa memcpy della finestra al suo interno. Poi consegni quella fetta a LoadDocument con Buffered = True, che la copia di nuovo nel buffer privato del componente. Due copie degli stessi byte, una delle quali pura cerimonia, e su una scansione di una grande casella di posta ripetuta per ogni messaggio. L'overload per byte range rimuove la prima copia incondizionatamente e la seconda opzionalmente
Cosa fa realmente l'overload per byte range
L'overload è sottile per progetto: valida, calcola un puntatore, e delega alla forma a puntatore di LoadDocument attraverso cui l'intera famiglia già confluisce. Index è a base zero, Count è una lunghezza in byte, e Buffered ha come predefinito True esattamente come sugli altri overload. Il LoadDocument(const Data: TBytes; Buffered: Boolean) a singolo argomento è ora esso stesso solo una chiamata a questo con Index = 0 e Count = Length(Data), così esiste un unico percorso di validazione invece di due
Chiamarlo assomiglia al codice che stavi già scrivendo, meno la fetta
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;
Perché Index più Count va in overflow nel controllo dei limiti?
Perché Index e Count sono entrambi Integer, e la somma di due grandi valori Integer positivi non è necessariamente un grande Integer positivo. Questo è il nucleo tecnico dell'overload, ed è l'unico punto dove un controllo dall'aspetto naturale è un buco di sicurezza della memoria. La formulazione ovvia è sbagliata
// 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');
Ripercorri il caso di fallimento. Prendi Index = 2000000000 e Count = 2000000000. La loro vera somma è quattro miliardi, ma in aritmetica con segno a 32 bit il risultato va in overflow ad esattamente meno 294.967.296. Quel valore è comodamente inferiore a Length(Data), così il controllo sbagliato passa, @Data[Index] viene preso ben fuori dall'array, e a PDFium viene consegnato un puntatore selvaggio più una lunghezza di due gigabyte. Ciò che segue è una violazione di accesso in un giorno buono e il parsing silenzioso di memoria di processo non correlata in uno cattivo
L'ordine corretto risolve questo non sommando mai. I valori negativi vengono rifiutati prima che qualsiasi cosa venga indicizzata, così @Data[Index] non può mai essere preso sotto l'array. Poi Index viene limitato da solo contro Length(Data), il che garantisce che Length(Data) - Index sia un Integer non negativo. Solo allora Count viene confrontato con quel resto. Ogni valore intermedio resta dentro l'intervallo rappresentabile, quindi nessuna configurazione di build può cambiare l'esito. Non farti tentare a fare affidamento sul controllo overflow {$Q+} come rete di sicurezza nemmeno: le build di release spediscono abitualmente con esso spento, e anche quando è acceso hai convertito un bug di sicurezza della memoria in un EIntOverflow che sfugge dal centro di una routine di validazione. PDFium Component tratta l'aritmetica di lunghezza non fidata allo stesso modo in cui tratta il resto del confine, una disciplina trattata più ampiamente in irrobustire l'ABI PDFium VCL e la sicurezza della memoria in Delphi
Perché una finestra a lunghezza zero deve passare nil?
Perché @Data[Index] non è un'espressione legale per ogni Index che la validazione accetta. Index = Length(Data) con Count = 0 è una finestra vuota perfettamente ben formata in coda al buffer, e un TBytes vuoto dà Index = 0 su un array che non ha alcun elemento zero affatto. Prendere l'indirizzo in entrambi i casi indicizza oltre la fine, o dereferenzia un array dinamico nil. Quindi l'overload si dirama: Count = 0 produce un puntatore nil, qualsiasi altro count produce @Data[Index]. Il nil poi fluisce nell'overload a puntatore, la cui protezione accetta un puntatore nil quando la dimensione è zero, e il caricamento termina nell'ordinario errore "Cannot load PDF document" invece di una violazione di accesso. Un chiamante che ha calcolato una finestra a zero byte da un contenitore malformato ottiene un pulito e catturabile EPdfError come qualsiasi altro input errato
Preso in prestito o copiato: cosa decide Buffered
Buffered seleziona il contratto di proprietà, ed è l'unico parametro qui con conseguenze oltre la chiamata. Con Buffered = True, PDFium Component copia la finestra selezionata, e solo la finestra, nel proprio buffer interno prima di caricare. Il contenitore da 40 MB non viene copiato; il PDF da 312 KB sì. Una volta che LoadDocument ritorna puoi liberare, riutilizzare o sovrascrivere il contenitore immediatamente, perché il componente non lo referenzia più. Questo è il predefinito e la scelta giusta per quasi tutto il codice
Buffered = False passa @Data[Index] direttamente a FPDF_LoadMemDocument64, e PDFium mantiene quel puntatore per la vita del documento invece di copiare i byte. Questo rende il caricamento privo di allocazioni, e rende l'intero TBytes sottostante una risorsa presa in prestito. Deve restare vivo e non modificato finché UnloadDocument non gira o Active non diventa False. Non la finestra, l'intero array: un array dinamico è a conteggio di riferimenti come unità, e lasciare andare l'ultimo riferimento in qualsiasi punto del tuo codice libera la memoria che PDFium sta ancora leggendo. Impostare Length su di esso è altrettanto fatale, perché una riallocazione può spostare il blocco. Dichiara questo nella tua stessa documentazione API ovunque esponi un tale caricamento, nello stesso spirito di qualsiasi altro confine prestito-contro-proprietà nel codice Pascal; la modalità di fallimento è identica ai pericoli di aliasing descritti in la fuga di FillChar e stringa di risultato in Delphi, dove un buffer sembra posseduto e non lo è
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;
Quando la finestra per byte range è lo strumento sbagliato
Sii onesto sul confine. L'overload per byte range presuppone che il contenitore sia già interamente in memoria, e Count è un Integer, quindi una singola finestra non può superare due gigabyte. Se il contenitore è un archivio da 6 GB su disco, o arriva su un socket che non puoi riavvolgere, questo overload non può aiutarti e leggere l'intera cosa in TBytes solo per indirizzare una finestra al suo interno vanifica lo scopo. È esattamente lì che appartiene il percorso FPDF_FILEACCESS, e l'articolo sullo streaming on demand mostra come esporre una vista con offset spostato di un file come sorgente documento personalizzata. Allo stesso modo, se i byte incorporati hanno bisogno di trasformazione prima che PDFium li veda, decompressione, decifratura, un passaggio di unwrapping, allora una vera copia è inevitabile e Buffered = True sull'array trasformato è la risposta onesta. La finestra per byte range paga esattamente in una forma: byte PDF contigui, non modificati, già residenti, a un offset noto
Se stai valutando questo per un viewer, un riquadro di anteprima o una pipeline di intake batch, l'overload per byte range e il loader in streaming sono due delle strategie di caricamento che PDFium Component distribuisce accanto ai caricamenti da file, stream e puntatore grezzo. La superficie API completa, la licenza e il supporto per le versioni Delphi e C++Builder sono documentati sulla pagina prodotto PDFium Component