PDFium Component peut ouvrir un PDF qui vit à l intérieur d un tampon plus grand directement depuis une plage d octets. La surcharge LoadDocument(const Data: TBytes; Index, Count: Integer; Buffered: Boolean) adresse une fenêtre sur place, donc aucun Copy préliminaire n est nécessaire. En échange, elle vous demande de comprendre une règle : quand Buffered vaut False, le tableau sous-jacent est emprunté, pas copié
C est un mécanisme différent de l approche pilotée par rappel décrite dans le streaming à la demande de gros PDF avec PDFium VCL, qui remet à PDFium un lecteur FPDF_FILEACCESS et le laisse tirer des blocs depuis le disque selon ses besoins. Celui-là est destiné aux documents trop volumineux pour tenir en RAM. Celui-ci est destiné aux documents déjà en RAM, assis à un décalage connu à l intérieur de quelque chose d autre. Les deux sont complémentaires, et la dernière section explique quelle situation appartient à laquelle
La copie de 40 Mo que personne n a demandée
Le scénario apparaît partout où des PDF voyagent à l intérieur d autres formats. Un magasin de messagerie conserve les corps de message et les pièces jointes dans un seul enregistrement. Un conteneur d archive concatène un manifeste, quelques images et un PDF. Un protocole filaire personnalisé encadre un document derrière un en-tête préfixé par une longueur. Dans chaque cas, vous finissez par détenir un grand TBytes et par savoir que le PDF commence à l octet 1 182 336 et court sur 312 kilo-octets
Avant que la surcharge par plage d octets n existe, la réponse idiomatique était Copy(Data, Index, Count), qui alloue un second tableau et memcpy la fenêtre dedans. Vous remettiez ensuite cette tranche à LoadDocument avec Buffered = True, qui la copie à nouveau dans le tampon privé du composant. Deux copies des mêmes octets, l une d elles pure cérémonie, et répétée pour chaque message sur un grand scan de boîte aux lettres. La surcharge par plage d octets supprime la première copie inconditionnellement et la seconde optionnellement
Ce que fait réellement la surcharge par plage d octets
La surcharge est mince par conception : elle valide, calcule un pointeur, et délègue à la forme pointeur de LoadDocument par laquelle toute la famille transite déjà. Index est basé sur zéro, Count est une longueur d octets, et Buffered vaut True par défaut exactement comme sur les autres surcharges. Le LoadDocument(const Data: TBytes; Buffered: Boolean) à argument unique n est lui-même désormais qu un appel à celui-ci avec Index = 0 et Count = Length(Data), donc il n y a qu un seul chemin de validation plutôt que deux
L appeler ressemble au code que vous écriviez déjà, moins la tranche
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;
Pourquoi Index plus Count déborde-t-il le contrôle de limites ?
Parce que Index et Count sont tous deux des Integer, et la somme de deux grandes valeurs Integer positives n est pas nécessairement un grand Integer positif. C est le cœur technique de la surcharge, et c est le seul endroit où une vérification d apparence naturelle est une faille de sécurité mémoire. La formulation évidente est fausse
// 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');
Suivez le cas d échec. Prenez Index = 2000000000 et Count = 2000000000. Leur somme réelle est quatre milliards, mais en arithmétique signée 32 bits, le résultat boucle vers exactement moins 294 967 296. Cette valeur est confortablement inférieure à Length(Data), donc la mauvaise vérification passe, @Data[Index] est pris bien en dehors du tableau, et PDFium reçoit un pointeur sauvage plus une longueur de deux gigaoctets. Ce qui suit est une violation d accès un bon jour et une analyse silencieuse de mémoire de processus sans rapport un mauvais jour
L ordre correct corrige cela en n additionnant jamais. Les valeurs négatives sont rejetées avant que quoi que ce soit ne soit indexé, donc @Data[Index] ne peut jamais être pris en dessous du tableau. Puis Index est borné à lui seul contre Length(Data), ce qui garantit que Length(Data) - Index est un Integer non négatif. Ce n est qu ensuite que Count est comparé à ce reste. Chaque valeur intermédiaire reste dans la plage représentable, donc aucune configuration de build ne peut changer le résultat. Ne soyez pas non plus tenté de vous fier à la vérification de débordement {$Q+} comme filet de sécurité : les builds de production l ont couramment désactivée, et même quand elle est activée, vous avez converti un bogue de sécurité mémoire en un EIntOverflow s échappant du milieu d une routine de validation. PDFium Component traite l arithmétique de longueur non fiable de la même façon qu il traite le reste de la limite, une discipline couverte plus largement dans le renforcement de l ABI PDFium VCL et de la sécurité mémoire en Delphi
Pourquoi une fenêtre de longueur zéro doit-elle passer nil ?
Parce que @Data[Index] n est pas une expression légale pour chaque Index que la validation accepte. Index = Length(Data) avec Count = 0 est une fenêtre vide parfaitement bien formée à la queue du tampon, et un TBytes vide donne Index = 0 sur un tableau qui n a pas d élément zéro du tout. Prendre l adresse dans l un ou l autre cas indexe au-delà de la fin, ou déréférence un tableau dynamique nil. Donc la surcharge se branche : Count = 0 produit un pointeur nil, tout autre compte produit @Data[Index]. Le nil s écoule alors dans la surcharge pointeur, dont sa propre garde accepte un pointeur nil quand la taille est zéro, et le chargement se termine par l erreur ordinaire « Cannot load PDF document » plutôt que par une violation d accès. Un appelant qui a calculé une fenêtre de zéro octet à partir d un conteneur malformé obtient un EPdfError propre et interceptable comme toute autre mauvaise entrée
Emprunté ou copié : ce que décide Buffered
Buffered sélectionne le contrat de propriété, et c est le seul paramètre ici avec des conséquences au-delà de l appel. Avec Buffered = True, PDFium Component copie la fenêtre sélectionnée, et seulement la fenêtre, dans son tampon interne avant le chargement. Le conteneur de 40 Mo n est pas copié ; le PDF de 312 Ko l est. Une fois que LoadDocument revient, vous pouvez libérer, réutiliser ou écraser le conteneur immédiatement, car le composant ne le référence plus. C est le comportement par défaut et le bon choix pour presque tout le code
Buffered = False transmet @Data[Index] directement à FPDF_LoadMemDocument64, et PDFium conserve ce pointeur pour la durée de vie du document plutôt que de copier les octets. Cela rend le chargement exempt d allocation, et cela fait de tout le TBytes sous-jacent une ressource empruntée. Il doit rester vivant et non modifié jusqu à ce que UnloadDocument s exécute ou que Active devienne False. Pas la fenêtre, le tableau entier : un tableau dynamique est à comptage de références en tant qu unité, et laisser partir la dernière référence n importe où dans votre code libère la mémoire que PDFium lit encore. Définir Length dessus est tout aussi fatal, car une réallocation peut déplacer le bloc. Énoncez cela dans votre propre documentation d API partout où vous exposez un tel chargement, dans le même esprit que toute autre limite emprunt-contre-possession dans le code Pascal ; le mode de défaillance est identique aux risques d aliasing décrits dans la fuite FillChar et chaîne de résultat en Delphi, où un tampon paraît possédé et ne l est pas
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;
Quand la fenêtre par plage d octets est le mauvais outil
Soyez honnête sur la limite. La surcharge par plage d octets suppose que le conteneur est déjà entièrement en mémoire, et Count est un Integer, donc une seule fenêtre ne peut pas dépasser deux gigaoctets. Si le conteneur est une archive de 6 Go sur disque, ou arrive via un socket que vous ne pouvez pas rembobiner, cette surcharge ne peut pas vous aider et lire l intégralité dans un TBytes juste pour adresser une fenêtre à l intérieur en manque l objectif. C est précisément là qu appartient le chemin FPDF_FILEACCESS, et l article sur le streaming à la demande montre comment exposer une vue à décalage décalé d un fichier comme source de document personnalisée. De même, si les octets intégrés ont besoin d une transformation avant que PDFium ne les voie, décompression, déchiffrement, une étape de déballage, alors une vraie copie est inévitable et Buffered = True sur le tableau transformé est la réponse honnête. La fenêtre par plage d octets rapporte dans exactement une forme : des octets PDF contigus, non modifiés, déjà résidents, à un décalage connu
Si vous évaluez cela pour un visualiseur, un volet d aperçu ou un pipeline d ingestion par lots, la surcharge par plage d octets et le chargeur en streaming sont deux des stratégies de chargement que PDFium Component livre aux côtés des chargements par fichier, flux et pointeur brut. La surface complète de l API, la licence et le support des versions Delphi et C++Builder sont documentés sur la page produit PDFium Component