Artikel Teknis

Pemuatan Byte Range PDFium untuk PDF Tertanam di Delphi

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

Diagram perbandingan yang menunjukkan jalur salin-ganda lama untuk PDF tersemat di dalam wadah di Delphi versus overload LoadDocument byte-range PDFium Component yang mengalamatkan jendela di tempat
Tanpa overload, jendela 312 KB yang sama disalin dua kali; panggilan byte-range mengalamatkannya di tempat dan Buffered memilih apakah ada yang disalin sama sekali

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;          // seluruh record kontainer, puluhan megabyte
  Offset, Size: Integer;
begin
  Frame := LoadContainerRecord('mailbox.dat');
  LocateEmbeddedPdf(Frame, Offset, Size);   // parser kontainer Anda

  // Tidak ada Copy(Frame, Offset, Size) di sini - window dialamatkan di tempat
  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

// SALAH: Index + Count dievaluasi dalam Integer dan bisa wrap menjadi negatif
if Index + Count <= Length(Data) then
  DataPtr := @Data[Index];

// BENAR: tolak tanda negatif lebih dulu, lalu batasi tiap suku secara terpisah,
// dengan satu-satunya aritmatika berupa pengurangan yang tidak bisa 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

Diagram alur yang mengontraskan pemeriksaan batas Index plus Count yang meluap dengan urutan validasi hanya-pengurangan yang menjaga pemuatan byte-range PDFium tetap aman memori di Delphi
Menambahkan lebih dulu melipat melewati MaxInt dan mengalahkan penjaganya; menolak tanda dan membatasi suku sebelum satu pengurangan bebas-lipat menjaga setiap perantara tetap terwakili

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;   // memiliki storage pendukung selama FPdf dimuat
    FPdf: TPdf;
  public
    procedure OpenEmbedded(Offset, Size: Integer);
    destructor Destroy; override;
  end;

procedure TFrameSession.OpenEmbedded(Offset, Size: Integer);
begin
  // Buffered = False: FFrame harus tetap hidup lebih lama dari dokumen yang dimuat
  FPdf.LoadDocument(FFrame, Offset, Size, False);
end;

destructor TFrameSession.Destroy;
begin
  FPdf.UnloadDocument;   // lepaskan pinjaman lebih dulu
  FFrame := nil;         // baru sekarang storage boleh dilepas
  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

Diagram timeline dua mode Buffered ketika memuat PDF tersemat dari rentang byte di Delphi: menyalin hanya jendela PDF dan membebaskan wadah segera versus meminjam seluruh TBytes penopang sampai UnloadDocument berjalan
Buffered = True menyalin hanya jendelanya sehingga container sekali pakai, sementara Buffered = False membiarkan seluruh array pendukung dipinjam sampai unload