Artikel Teknis

HotXLS di Free Pascal: Unicode, Slot COM, dan zlib

HotXLS dibangun di bawah Free Pascal dan Lazarus di Windows, dan porting ini bergantung pada empat keputusan yang tak ada hubungannya dengan sintaks Object Pascal: pertahankan core dalam mode DELPHIUNICODE, deklarasikan interface structured-storage OLE sebagai interface CORBA dengan reference counting yang dikelola manual, ganti file objek AES Win32 dengan implementasi Pascal, dan perbaiki loop inflate yang bisa menerima ZIP terpotong sebagai lengkap

Siapa pun yang pernah melakukan porting pustaka Delphi matang tahu bentuk pekerjaan ini. Kompilator menerima hampir semuanya di pass pertama. Yang menyusul adalah ekor panjang perbedaan perilaku yang terkompilasi bersih tapi menghasilkan hasil yang salah, dan engine spreadsheet luar biasa terpapar karena menyentuh encoding teks, structured storage COM, kompresi, dan kriptografi dalam satu jalur kode

Mengapa core bersikeras DELPHIUNICODE, bukan DELPHI polos?

Karena engine formula bergantung pada String dan Char yang membawa semantik UTF-16, dan alternatif ANSI kehilangan karakter sebelum apa pun sampai ke file. Menggoda untuk membangun core dalam mode DELPHI FPC, sebab itu switch kompatibilitas yang paling sering diraih porting, dan kodenya terkompilasi. Lalu workbook dengan nama sheet Mandarin atau label Kiril melakukan round-trip melalui jalur perhitungan dan karakternya lenyap pada saat writer melihatnya, tanpa error di mana pun

Modenya tidak seragam di seluruh pustaka, dan itu disengaja, bukan berantakan. Dekoder byte PNG dan override LCL memang butuh signature ANSI, karena keduanya berurusan dengan byte dan dengan apa yang diserahkan widgetset. Unit-unit itu menyalakan switch LX_FPC_ANSI terpisah. Dua mode dalam satu pustaka terdengar seperti code smell sampai Anda menyadari alternatifnya adalah dekoder byte yang memperlakukan inputnya sebagai teks

Ada satu detail pendamping yang menjebak orang belakangan. DELPHIUNICODE tidak menjadikan TFormatSettings.DecimalSeparator sebuah WideChar di runtime FPC. Input yang membawa pemisah desimal Unicode harus dinormalkan ke pemisah ASCII di dalam string Unicode lebih dulu, dan input mana pun yang pemisahnya tidak cocok dengan yang diharapkan harus ditolak alih-alih terpotong diam-diam di karakter yang tak dikenali parser

program ExportReport;
{$MODE DELPHI}
uses
  Interfaces,          // wajib paling awal: menginisialisasi widgetset LCL
  SysUtils, lxHandle;  // serta lapisan konversi UTF-8

var
  Book: TXLSWorkbook;
begin
  Book := TXLSWorkbook.Create(nil);
  try
    Book.LoadFromFile('input.xls');
    Book.Sheets[0].AsString[1, 1] := 'Quarterly summary';
    Book.SaveToFile('output.xls');
  finally
    Book.Free;
  end;
end.

Unit Interfaces tidak opsional dan harus paling awal. Ia yang menginisialisasi widgetset LCL dan lapisan konversi UTF-8, dan HotXLS bergantung pada keduanya begitu font, path file, atau teks menyeberangi batas RTL dan LCL. Program konsol yang melewatinya akan terkompilasi dan akan bersikap salah di path non-ASCII mana pun. Ini juga alasan mengapa kompilasi yang sukses membuktikan sedikit sekali di sini: porting itu baru terbukti bekerja begitu dokumen nyata dengan nama font dan path nyata menempuh round-trip penuh

VMT class bukan vtable COM

Free Pascal tidak akan membiarkan Anda menyerahkan VMT class ke Windows sebagai vtable interface COM, bahkan ketika deklarasinya tampak identik dengan yang diterima Delphi. Tata letaknya berbeda dengan cara yang menghasilkan panggilan ke slot yang salah, yang bermanifestasi sebagai crash di tempat yang tak berkaitan dengan call site. Structured storage penting di sini karena format workbook biner klasik adalah compound file OLE, dan membaca atau menulisnya berarti mengimplementasikan ILockBytes yang akan dipanggil balik oleh API storage Windows

Susunan yang bekerja adalah interface CORBA dengan slot COM yang dideklarasikan eksplisit dan AddRef serta Release yang dikelola manual. Artinya melepas reference counting otomatis untuk tipe-tipe ini dan mengambil tanggung jawab atas lifetime-nya — pertukaran yang adil untuk segelintir interface yang hidup di dalam satu unit. Jebakan spesifik di dalam pekerjaan itu adalah QueryInterface: ia harus mengembalikan pointer interface, bukan pointer objek. Keduanya terkompilasi. Satu di antaranya menyerahkan ke Windows sebuah alamat yang machine word pertamanya bukan vtable

Diagram yang membandingkan VMT class Free Pascal dengan vtable interface COM yang harus disajikan HotXLS ke API structured storage Windows: urutan slot berbeda untuk deklarasi Pascal yang sama, plus jebakan QueryInterface di mana mengembalikan pointer objek alih-alih pointer interface mengirim panggilan ILockBytes ke slot class dan crash jauh dari call site
Free Pascal menolak menyajikan VMT class sebagai vtable COM, jadi HotXLS mendeklarasikan interface CORBA dengan slot COM eksplisit serta AddRef dan Release yang dikelola manual, dan QueryInterface mengembalikan pointer interface yang bisa di-dereference Windows

Deklarasi khusus FPC tinggal di lxOleInterfaces.inc, di samping lxAESBackend.inc dan lxZlibBackend.inc di direktori source FPC, sehingga pilihan yang spesifik kompilator duduk di satu tempat alih-alih tersebar di sepanjang engine. Formatnya sendiri dan bagaimana pustaka menavigasinya dideskripsikan di membaca compound file OLE2 di Pascal

Satu detail tipe lagi termasuk keluarga yang sama. LargeInt harus di-resolve ke Int64 di cabang FPC, dan klasifikasi kompilator atas Comp cukup berbeda di kedua toolchain sehingga resolusi overload bisa memilih kandidat yang berbeda. Uji perilaku offset besar dengan file stream, bukan HGLOBAL stream: stream global-memory Windows wrap dengan sendirinya pada seek melewati 4 GiB, jadi test yang lolos di sana tidak membuktikan apa pun tentang aritmetika Anda sendiri

Apa yang disembunyikan implementasi AES yang konsisten dengan dirinya sendiri

File objek AES Win32 yang ditautkan build Delphi berformat OMF, dan linker Free Pascal tak bisa mengonsumsinya, jadi cabang FPC memakai implementasi AES Pascal sebagai gantinya. Delphi terus menautkan file objek yang selalu ditautkannya, sehingga biner yang dirilis tetap tak berubah bagi pelanggan yang ada

Tuntutan verifikasinya adalah bagian yang layak dibawa ke proyek mana pun. Mengenkripsi data lalu mendekripsinya lagi dengan implementasi yang sama tidak membuktikan apa pun: algoritma simetris dengan key schedule yang salah, urutan blok yang salah, atau chaining yang salah itu konsisten dengan dirinya sendiri dengan sempurna dan akan melakukan round-trip atas outputnya sendiri setiap kali. Hanya known-answer vectors yang menangkapnya, memeriksa ekspansi key, urutan blok, dan chaining CBC terhadap nilai yang dipublikasikan. Kirim implementasi yang salah tapi konsisten dengan dirinya sendiri, dan gejalanya muncul saat pertama kali pelanggan membuka file di Excel

Kompresi punya cacat dengan karakter berbeda. Backend inflate Pascal bisa saja masih menyimpan output tertunda setelah mengonsumsi seluruh input terkompresinya, jadi pemanggil harus terus memanggil sampai stream melaporkan akhirnya. Memperlakukan input yang habis sebagai end-of-stream memotong blok terakhir. Lebih buruk, itu mengubah arsip rusak menjadi arsip yang diterima diam-diam — persis mode kegagalan yang ingin dicegah oleh penguatan di memvalidasi record ZIP end-of-central-directory. Aturannya: tanpa kemajuan plus belum selesai adalah error truncation, bukan EOF

Dua jebakan build system yang memakan jam sungguhan

Path pencarian LCL harus mendahului path wildcard paket FPC, kalau tidak unit Menus milik Free Vision membayangi unit LCL bernama sama dan Anda mendapat ketidakcocokan checksum PPU yang tidak mengatakan apa-apa tentang keduanya. Instalasi Lazarus yang dipindah setelah terpasang juga bisa menyisakan path basi di fpc.cfg, sehingga entry point build menetapkan path unit dan biner secara eksplisit alih-alih mewarisi apa pun yang ditawarkan lingkungan

Jebakan kedua tak ada hubungannya dengan Pascal. File batch .cmd yang ditulis dengan akhiran baris LF bekerja sampai file itu tumbuh melampaui ukuran buffer baca interpreter, di titik mana call :label gagal dengan klaim bahwa label batch tidak ada, dan kegagalannya muncul di program apa pun yang kebetulan duduk melampaui batas. Tool apa pun yang menulis ulang script batch harus menulis CRLF kembali. Dan lazbuild --build-all membersihkan direktori output unit paket sebelum mengompilasi, sehingga file opsi yang diparkir di direktori itu terhapus sebelum sempat dibaca: simpan di luar, dan ingat path @ di-resolve relatif terhadap direktori paket karena lazbuild memanggil kompilator dari sana

Peta dua lapisan bahaya di balik kompilasi Free Pascal HotXLS yang bersih: mode DELPHIUNICODE yang menjaga String dan Char tetap UTF-16, pelarian LX_FPC_ANSI untuk dekoder byte PNG dan override LCL, serta jebakan build dari bayangan Menus Free Vision, path fpc.cfg basi, file batch LF-saja, dan penghapusan output lazbuild
Kompilasi pertama membuktikan sedikit: peta mode menentukan karakter mana yang selamat sampai ke writer, sementara jebakan build system bermunculan sebagai ketidakcocokan checksum, label hilang phanton, dan file opsi terhapus sebelum dibaca
// Ekspor grid Lazarus: TGridToXLS tersedia dalam paket Lazarus, jadi kode
// ekspor DB-grid yang sama bekerja di aplikasi LCL
var
  Exporter: TGridToXLS;
begin
  Exporter := TGridToXLS.Create(nil);
  try
    Exporter.DBGrid := GridOrders;
    Exporter.WorksheetName := 'Orders';
    Exporter.ExportHeader := True;
    Exporter.SetColumnsWidth := True;
    Exporter.ExportDBGrid;
    Exporter.SaveAs('orders.xls');
  finally
    Exporter.Free;
  end;
end;

Untuk apa peringatan kompilator itu

Free Pascal melaporkan variabel lokal yang tak diinisialisasi, yang tidak dilakukan Delphi, dan menjalankan build FPC mengubah selisih itu menjadi dua cacat nyata di unit perhitungan. Satu fungsi membaca variabel hitungan yang tak pernah diisi sebelum dipakai, dan fungsi lain memakai dua koordinat di satu cabang sebelum kode yang menghitungnya berjalan di cabang lain. Di bawah Delphi keduanya berperilaku sesuai apa pun yang kebetulan dipegang stack — itulah definisi bug yang mereproduksi di satu mesin dan tidak di mesin lain

Kesimpulan praktisnya: kompilator kedua layak dipertahankan dalam lingkaran bahkan untuk produk yang mengirim terutama di yang pertama. Memindai kelas-kelas peringatan FPC secara berkala adalah pass analisis statis yang murah atas codebase Delphi, dan ia menemukan kategori cacat yang tak andal dijangkau test suite mana pun. Disiplin matrix versi yang lebih luas tempat ini duduk dibahas di matrix build lintas-kompilator

Dukungan Free Pascal dan Lazarus untuk Windows tersedia dalam komponen spreadsheet Delphi HotXLS sebagai paket Lazarus berdampingan dengan paket Delphi dan C++Builder, dibangun dari source tree yang sama alih-alih sebuah fork. Itulah inti latihannya: satu engine, empat toolchain, dan keputusan yang spesifik kompilator terisolasi di include file tempat semuanya bisa dibaca dalam sekali duduk