PDFium Component bisa membuka PDF yang berada di dalam buffer lebih besar langsung dari sebuah byte range. Overload LoadDocument(const Data: TBytes; Index, Count: Integer; Buffered: Boolean) mengalamati sebuah window di tempatnya, jadi tidak dibutuhkan Copy pendahuluan. Sebagai gantinya, ia meminta Anda memahami satu aturan: saat Buffered adalah False, array pendukungnya dipinjam, bukan disalin
Ini adalah mekanisme berbeda dari pendekatan berbasis callback yang dijelaskan di streaming PDF besar sesuai permintaan dengan PDFium VCL, yang menyerahkan reader FPDF_FILEACCESS ke PDFium dan membiarkannya menarik blok dari disk sesuai kebutuhan. Itu untuk dokumen yang terlalu besar untuk disimpan di RAM. Ini untuk dokumen yang sudah ada di RAM, berada di offset yang diketahui di dalam sesuatu yang lain. Keduanya saling melengkapi, dan bagian terakhir menjelaskan situasi mana yang milik yang mana
Salinan 40 MB yang tidak diminta siapa pun
Skenario ini muncul di mana pun PDF berpindah di dalam format lain. Mail store menyimpan body pesan dan lampiran dalam satu record. Kontainer arsip menyambungkan manifest, beberapa gambar dan satu PDF. Protokol wire kustom membingkai dokumen di belakang header berawalan panjang. Dalam setiap kasus Anda berakhir memegang satu TBytes besar dan tahu bahwa PDF-nya mulai di byte 1.182.336 dan berjalan sepanjang 312 kilobyte
Sebelum overload byte range ini ada, jawaban idiomatiknya adalah Copy(Data, Index, Count), yang mengalokasikan array kedua dan melakukan memcpy window itu ke dalamnya. Anda lalu menyerahkan irisan itu ke LoadDocument dengan Buffered = True, yang menyalinnya lagi ke buffer privat komponen. Dua salinan dari byte yang sama, satu di antaranya murni seremoni, dan pada scan mailbox besar berulang untuk setiap pesan. Overload byte range menghilangkan salinan pertama secara tanpa syarat dan salinan kedua secara opsional
Apa sebenarnya yang dilakukan overload byte range itu
Overload ini tipis secara desain: ia memvalidasi, menghitung satu pointer, dan mendelegasikan ke bentuk pointer dari LoadDocument yang seluruh keluarganya sudah mengalir lewatnya. Index berbasis nol, Count adalah panjang byte, dan Buffered defaultnya True persis seperti pada overload lainnya. LoadDocument(const Data: TBytes; Buffered: Boolean) yang argumen tunggal sekarang sendirinya hanyalah pemanggilan ke yang ini dengan Index = 0 dan Count = Length(Data), jadi hanya ada satu jalur validasi, bukan dua
Memanggilnya terlihat seperti kode yang sudah Anda tulis, minus irisannya
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;
Kenapa Index plus Count meng-overflow pemeriksaan batas?
Karena Index dan Count keduanya Integer, dan jumlah dari dua nilai Integer positif besar tidak selalu menjadi Integer positif besar. Inilah inti teknis dari overload ini, dan inilah satu-satunya tempat di mana pemeriksaan yang terlihat wajar justru lubang keamanan memori. Rumusan yang tampak jelas justru salah
// 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');
Telusuri kasus kegagalannya. Ambil Index = 2000000000 dan Count = 2000000000. Jumlah sebenarnya adalah empat miliar, tapi dalam aritmatika signed 32-bit hasilnya wrap menjadi persis minus 294.967.296. Nilai itu jelas lebih kecil dari Length(Data), jadi pemeriksaan yang salah itu lolos, @Data[Index] diambil jauh di luar array, dan PDFium diserahi pointer liar plus panjang dua gigabyte. Yang mengikuti adalah access violation pada hari baik dan parsing diam-diam atas memori proses yang tidak berhubungan pada hari buruk
Urutan yang benar memperbaiki ini dengan tidak pernah menambahkan. Nilai negatif ditolak sebelum apa pun diindeks, sehingga @Data[Index] tidak pernah bisa diambil di bawah array. Lalu Index dibatasi sendiri terhadap Length(Data), yang menjamin Length(Data) - Index adalah Integer non-negatif. Baru setelah itu Count dibandingkan terhadap sisa itu. Setiap nilai perantara tetap dalam rentang yang bisa direpresentasikan, jadi konfigurasi build apa pun tidak bisa mengubah hasilnya. Jangan tergoda mengandalkan pemeriksaan overflow {$Q+} sebagai jaring pengaman juga: build rilis rutin dikirim dengan itu mati, dan bahkan saat aktif Anda hanya mengubah bug keamanan memori menjadi EIntOverflow yang lolos dari tengah rutin validasi. PDFium Component memperlakukan aritmatika panjang tidak tepercaya dengan cara yang sama seperti sisa batasnya, disiplin yang dibahas lebih luas di memperkuat ABI dan keamanan memori PDFium VCL di Delphi
Kenapa window panjang-nol harus meneruskan nil?
Karena @Data[Index] bukan ekspresi legal untuk setiap Index yang diterima validasi. Index = Length(Data) dengan Count = 0 adalah window kosong yang sepenuhnya sah di ekor buffer, dan TBytes kosong memberikan Index = 0 pada array yang bahkan tidak punya elemen nol sama sekali. Mengambil alamat pada kedua kasus itu mengindeks melewati akhir, atau men-dereferensi array dinamis nil. Jadi overload-nya bercabang: Count = 0 menghasilkan pointer nil, count lain apa pun menghasilkan @Data[Index]. Nil itu lalu mengalir ke overload pointer, yang guard-nya sendiri menerima pointer nil saat ukurannya nol, dan pemuatannya berakhir dalam error "Cannot load PDF document" biasa alih-alih access violation. Pemanggil yang menghitung window nol-byte dari kontainer yang cacat mendapat EPdfError yang bersih dan bisa ditangkap seperti input buruk lainnya
Dipinjam atau disalin: apa yang diputuskan Buffered
Buffered memilih kontrak kepemilikan, dan itu satu-satunya parameter di sini yang punya konsekuensi melampaui pemanggilan itu sendiri. Dengan Buffered = True, PDFium Component menyalin window yang dipilih, dan hanya window itu, ke buffer internalnya sebelum memuat. Kontainer 40 MB tidak disalin; PDF 312 KB-nya yang disalin. Begitu LoadDocument kembali, Anda boleh langsung membebaskan, memakai ulang atau menimpa kontainernya, karena komponen tidak lagi merujuknya. Ini adalah default dan pilihan yang tepat untuk hampir semua kode
Buffered = False meneruskan @Data[Index] langsung ke FPDF_LoadMemDocument64, dan PDFium menyimpan pointer itu selama umur dokumen alih-alih menyalin byte-nya. Itu membuat pemuatannya bebas-alokasi, dan itu membuat seluruh TBytes pendukungnya menjadi resource pinjaman. Ia harus tetap hidup dan tidak dimodifikasi sampai UnloadDocument berjalan atau Active menjadi False. Bukan hanya window-nya, tapi seluruh array: array dinamis di-reference-count sebagai satu unit, dan membiarkan referensi terakhir lepas di mana pun dalam kode Anda membebaskan memori yang masih dibaca PDFium. Mengatur Length padanya juga sama fatalnya, karena realokasi bisa memindahkan blok tersebut. Nyatakan ini dalam dokumentasi API Anda sendiri di mana pun Anda mengekspos pemuatan semacam itu, dengan semangat yang sama seperti batas pinjam-versus-miliki lain mana pun dalam kode Pascal; mode kegagalannya identik dengan bahaya aliasing yang dijelaskan di kebocoran FillChar dan result string di Delphi, di mana buffer terlihat dimiliki padahal tidak
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;
Saat window byte range adalah alat yang salah
Bersikaplah jujur soal batasannya. Overload byte range mengasumsikan kontainernya sudah sepenuhnya di memori, dan Count adalah Integer, jadi satu window tidak bisa melebihi dua gigabyte. Jika kontainernya adalah arsip 6 GB di disk, atau tiba lewat socket yang tidak bisa Anda rewind, overload ini tidak bisa membantu Anda dan membaca seluruh isinya ke TBytes hanya untuk mengalamati window di dalamnya justru mengalahkan tujuannya. Di situlah persis jalur FPDF_FILEACCESS berada, dan artikel streaming sesuai permintaan menunjukkan cara mengekspos tampilan tergeser-offset dari sebuah file sebagai sumber dokumen kustom. Sama pentingnya, jika byte tertanam butuh transformasi sebelum dilihat PDFium, dekompresi, dekripsi, langkah unwrapping, maka salinan sungguhan tidak terhindarkan dan Buffered = True pada array yang sudah ditransformasi adalah jawaban yang jujur. Window byte range membayar hasilnya dalam persis satu bentuk: byte PDF yang bersambung, tidak dimodifikasi, sudah resident, pada offset yang diketahui
Jika Anda mengevaluasi ini untuk sebuah viewer, panel pratinjau atau pipeline intake batch, overload byte range dan loader streaming adalah dua dari strategi pemuatan yang dikirim PDFium Component bersama pemuatan file, stream dan raw pointer. Seluruh permukaan API, lisensi dan dukungan versi Delphi dan C++Builder didokumentasikan di halaman produk PDFium Component