PDFium Component, daha büyük bir arabelleğin içinde yaşayan bir PDF'i doğrudan bir byte aralığından açabilir. LoadDocument(const Data: TBytes; Index, Count: Integer; Buffered: Boolean) aşırı yüklemesi bir pencereyi yerinde adresler, dolayısıyla ön hazırlık bir Copy gerekmez. Karşılığında sizden bir kuralı anlamanızı ister: Buffered False olduğunda, arkasındaki dizi kopyalanmaz, ödünç alınır
Bu, PDFium VCL ile büyük PDF'leri talep üzerine akıtmada anlatılan, PDFium'a bir FPDF_FILEACCESS okuyucusu veren ve ihtiyaç duyduğunda blokları diskten çekmesine izin veren geri çağırma tabanlı yaklaşımdan farklı bir mekanizmadır. O, RAM'de tutulamayacak kadar büyük belgeler içindir. Bu ise zaten RAM'de, başka bir şeyin içinde bilinen bir uzaklıkta oturan belgeler içindir. İkisi tamamlayıcıdır ve son bölüm hangi durumun hangisine ait olduğunu açıklar
Kimsenin istemediği 40 MB'lık kopya
Senaryo, PDF'lerin başka formatların içinde seyahat ettiği her yerde ortaya çıkar. Bir posta deposu mesaj gövdelerini ve ekleri tek bir kayıtta tutar. Bir arşiv konteyneri bir manifesti, birkaç görüntüyü ve bir PDF'i birleştirir. Özel bir kablo protokolü bir belgeyi uzunluk-öneki olan bir başlığın arkasında çerçeveler. Her durumda, PDF'in 1.182.336 baytta başladığını ve 312 kilobayt sürdüğünü bilerek büyük bir TBytes elinizde kalır
Byte aralığı aşırı yüklemesi var olmadan önce, deyimsel cevap Copy(Data, Index, Count) idi, ki bu ikinci bir dizi tahsis eder ve pencereyi onun içine memcpy'ler. Sonra o dilimi Buffered = True ile LoadDocument'e verirsiniz, ki bu onu bileşenin özel arabelleğine tekrar kopyalar. Aynı baytların iki kopyası, biri saf törensellik ve büyük bir posta kutusu taramasında her mesaj için tekrarlanır. Byte aralığı aşırı yüklemesi ilk kopyayı koşulsuz olarak ve ikinciyi isteğe bağlı olarak kaldırır
Byte aralığı aşırı yüklemesi gerçekte ne yapar?
Aşırı yükleme tasarım gereği incedir: doğrular, bir işaretçi hesaplar ve tüm ailenin zaten aktığı LoadDocument'in işaretçi biçimine devreder. Index sıfır tabanlıdır, Count bir byte uzunluğudur ve Buffered varsayılan olarak diğer aşırı yüklemelerde olduğu gibi tam olarak True'dur. Tek argümanlı LoadDocument(const Data: TBytes; Buffered: Boolean)'in kendisi artık Index = 0 ve Count = Length(Data) ile buna bir çağrıdır, dolayısıyla iki yerine bir doğrulama yolu vardır
Onu çağırmak, dilim eksi, zaten yazmakta olduğunuz kod gibi görünür
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;
Index artı Count sınır kontrolünü neden taşırıyor?
Çünkü Index ve Count'un ikisi de Integer'dır ve iki büyük pozitif Integer değerinin toplamı zorunlu olarak büyük pozitif bir Integer değildir. Bu, aşırı yüklemenin teknik özüdür ve doğal görünen bir kontrolün bir bellek güvenliği deliği olduğu tek yerdir. Bariz formülasyon yanlıştır
// 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');
Başarısız olan durumu inceleyin. Index = 2000000000 ve Count = 2000000000 alın. Gerçek toplamları dört milyardır, ama 32 bit imzalı aritmetikte sonuç tam olarak eksi 294.967.296'ya sarar. Bu değer Length(Data)'dan rahatça küçüktür, dolayısıyla yanlış kontrol geçer, @Data[Index] dizinin çok dışından alınır ve PDFium'a rastgele bir işaretçi artı iki gigabaytlık bir uzunluk verilir. Ardından gelen, iyi bir günde bir erişim ihlali ve kötü bir günde alakasız süreç belleğinin sessiz ayrıştırılmasıdır
Doğru sıralama bunu asla eklemeyerek düzeltir. Negatifler herhangi bir şey indekslenmeden önce reddedilir, dolayısıyla @Data[Index] asla dizinin altından alınamaz. Sonra Index, Length(Data)'ya karşı kendi başına sınırlandırılır, ki bu Length(Data) - Index'in negatif olmayan bir Integer olmasını garanti eder. Ancak o zaman Count o kalanla karşılaştırılır. Her ara değer temsil edilebilir aralık içinde kalır, dolayısıyla hiçbir derleme yapılandırması sonucu değiştiremez. {$Q+} taşma kontrolüne bir güvenlik ağı olarak güvenmeye de kalkışmayın: sürüm derlemeleri rutin olarak onu kapalı gönderir ve açık olsa bile, bir bellek güvenliği hatasını bir doğrulama rutininin ortasından kaçan bir EIntOverflow'a dönüştürmüş olursunuz. PDFium Component, güvenilmeyen uzunluk aritmetiğini, Delphi'de PDFium VCL ABI'yi ve bellek güvenliğini sertleştirmede daha geniş ele alınan bir disiplinle, sınırın geri kalanıyla aynı şekilde ele alır
Sıfır uzunluklu bir pencere neden nil geçmelidir?
Çünkü @Data[Index], doğrulamanın kabul ettiği her Index için yasal bir ifade değildir. Count = 0 olan Index = Length(Data), arabelleğin kuyruğunda mükemmel derecede iyi biçimlendirilmiş boş bir penceredir ve boş bir TBytes, hiç sıfır elemanı olmayan bir dizide Index = 0 verir. Her iki durumda da adresi almak, sonun ötesinde indeksler veya bir nil dinamik diziyi dereferans eder. Dolayısıyla aşırı yükleme dallanır: Count = 0 bir nil işaretçi verir, başka herhangi bir sayı @Data[Index] verir. Nil daha sonra işaretçi aşırı yüklemesine akar, ki bu kendi koruması boyut sıfır olduğunda bir nil işaretçiyi kabul eder ve yükleme, bir erişim ihlali yerine sıradan "Cannot load PDF document" hatasıyla biter. Biçimsiz bir konteynerden sıfır baytlık bir pencere hesaplayan bir çağıran, diğer herhangi bir kötü girdi gibi temiz, yakalanabilir bir EPdfError alır
Ödünç mü kopyalanmış mı: Buffered'in kararlaştırdığı şey
Buffered, sahiplik sözleşmesini seçer ve burada çağrının ötesinde sonuçları olan tek parametredir. Buffered = True ile, PDFium Component, yüklemeden önce seçili pencereyi, ve yalnızca pencereyi, dahili arabelleğine kopyalar. 40 MB'lık konteyner kopyalanmaz; 312 KB'lık PDF kopyalanır. LoadDocument döndükten sonra, konteyneri hemen serbest bırakabilir, yeniden kullanabilir veya üzerine yazabilirsiniz, çünkü bileşen artık ona referans vermez. Bu varsayılandır ve neredeyse tüm kod için doğru seçimdir
Buffered = False, @Data[Index]'i doğrudan FPDF_LoadMemDocument64'e geçirir ve PDFium, baytları kopyalamak yerine o işaretçiyi belgenin ömrü boyunca tutar. Bu, yüklemeyi tahsissiz yapar ve arkasındaki tüm TBytes'i ödünç alınan bir kaynak yapar. UnloadDocument çalışana veya Active False olana kadar canlı ve değiştirilmemiş kalmalıdır. Pencere değil, tüm dizi: dinamik bir dizi bir birim olarak referans sayılır ve kodunuzda herhangi bir yerde son referansın gitmesine izin vermek, PDFium'un hâlâ okuduğu belleği serbest bırakır. Üzerinde Length'i ayarlamak da tam olarak aynı şekilde ölümcüldür, çünkü bir yeniden tahsis bloğu taşıyabilir. Böyle bir yükü açığa vurduğunuz her yerde bunu kendi API dokümantasyonunuzda belirtin, Pascal kodundaki diğer herhangi bir ödünç-alma-karşı-sahip-olma sınırıyla aynı ruhla; hata modu, bir arabelleğin sahiplenilmiş göründüğü ama olmadığı Delphi'de FillChar ve sonuç dizesi sızıntısında anlatılan takma ad tehlikeleriyle özdeştir
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;
Byte aralığı penceresinin yanlış araç olduğu zaman
Sınır konusunda dürüst olun. Byte aralığı aşırı yüklemesi, konteynerin zaten tamamen bellekte olduğunu varsayar ve Count bir Integer'dır, dolayısıyla tek bir pencere iki gigabaytı aşamaz. Konteyner diskte 6 GB'lık bir arşivse, veya geri saramadığınız bir soket üzerinden geliyorsa, bu aşırı yükleme size yardımcı olamaz ve içindeki bir pencereyi adreslemek için tümünü TBytes'e okumak amacını yener. Burası, tam olarak FPDF_FILEACCESS yolunun ait olduğu yerdir ve talep üzerine akış makalesi, bir dosyanın uzaklık kaymış bir görünümünü özel bir belge kaynağı olarak nasıl açığa vuracağınızı gösterir. Aynı şekilde, gömülü baytların PDFium onları görmeden önce dönüşüme ihtiyacı varsa, sıkıştırma açma, şifre çözme, bir açma adımı, o zaman gerçek bir kopya kaçınılmazdır ve dönüştürülmüş dizide Buffered = True dürüst cevaptır. Byte aralığı penceresi tam olarak bir şekilde kazanır: bitişik, değiştirilmemiş PDF baytları, zaten yerleşik, bilinen bir uzaklıkta
Bunu bir görüntüleyici, bir önizleme paneli veya bir toplu alım boru hattı için değerlendiriyorsanız, byte aralığı aşırı yüklemesi ve akış tabanlı yükleyici, PDFium Component'in dosya, akış ve ham işaretçi yüklemelerinin yanında gönderdiği yükleme stratejilerinden ikisidir. Tam API yüzeyi, lisanslama ve Delphi ile C++Builder sürüm desteği PDFium Component ürün sayfasında belgelenmiştir