Jika satu-satunya tugas server adalah menghasilkan file Excel, server itu tidak punya urusan menjalankan Excel. Menginstal Office pada build agent atau layanan laporan untuk mengendalikannya melalui otomatisasi COM adalah desain yang keliru, dan desain itu sudah keliru sejak praktik ini ada. Microsoft sendiri menyatakannya, dalam panduan yang tidak melunak selama dua puluh tahun: Office tidak dibangun maupun dilisensikan untuk diotomatisasi dari proses sisi server yang tidak diawasi. Jawaban yang benar adalah menulis byte BIFF dan OOXML secara langsung, tanpa Excel sama sekali dalam gambarannya. Itulah premis utuh dari HotXLS, pustaka Object Pascal native yang membaca dan menulis format spreadsheet itu sendiri, sehingga tidak ada aplikasi desktop yang bisa hang, bocor memori, atau perlu dibayar per kursi
Mengapa Menjalankan EXCEL.EXE dari Layanan Gagal
Otomatisasi COM mengendalikan program desktop dari jarak jauh, dan program desktop secara diam-diam mengasumsikan tiga hal yang tidak bisa diberikan oleh layanan Windows: profil pengguna yang termuat, window station interaktif, dan manusia yang mengawasi layar. Hilangkan ketiganya dan kegagalan itu muncul dalam bentuk yang tidak pernah bisa direproduksi di mesin developer mana pun. Prompt pemulihan file, error add-in, atau dialog aktivasi lisensi terbuka di desktop yang tidak bisa dilihat siapa pun, dan panggilan otomatisasi yang memicunya tidak pernah kembali. Pemanggil pada akhirnya time out dan mati; instance Excel sering kali tidak, bertahan sebagai proses yatim yang memegang file lock dan meracuni proses berikutnya. Siapa pun yang pernah melihat sebelas proses EXCEL.EXE liar menumpuk di bawah akun layanan tahu kelanjutan cerita itu
Cerita skalabilitasnya pun tidak lebih baik bahkan ketika tidak ada yang crash. Satu instance Excel adalah pipeline satu-workbook, setiap akses properti membayar biaya cross-process COM marshaling, dan mesin yang menjalankan kode itu membawa lisensi Office yang ketentuannya justru mengecualikan penggunaan semacam ini. Kebanyakan tim menemui batas-batas ini satu outage demi satu outage, dan begitulah kira-kira "pensiunkan lapisan COM" berakhir masuk ke roadmap
Sebelum penulisan ulang itu dimulai, selesaikan satu pertanyaan cakupan lebih dulu, karena itu menentukan seberapa nyata pekerjaan yang harus dikerjakan. Kode COM hampir tidak pernah sekadar mengisi nilai sel. Kode itu memanggil Workbook.SaveAs dengan konstanta format, memaksa rekalkulasi, mendorong pengaturan cetak, kadang menyentuh clipboard. Telusuri kode lama itu dan catat perilaku mana saja yang benar-benar muncul di output, karena masing-masing mendarat di sudut berbeda dari pustaka native, dan beberapa di antaranya (interop clipboard adalah contoh paling jelas) tidak punya makna di sisi server dan sebaiknya dibuang saja alih-alih diporting
Dua Mesin Native, Dua Model Kepemilikan
HotXLS menggantikan proses Excel dengan dua implementasi format langsung. Mesin record-stream BIFF8 (TXLSWorkbook, unit lxHandle) menangani .xls. Penulis paket OOXML (TXLSXWorkbook, unit lxHandleX) menghasilkan .xlsx yang sesuai dengan ECMA-376 / ISO/IEC 29500. Tidak ada yang perlu didaftarkan dan tidak ada yang perlu diinstal di server, dan Anda bisa membuka workbook sebanyak yang diizinkan oleh memori secara bersamaan
Yang sering membuat orang tersandung sejak awal adalah kedua facade ini memiliki cara berbeda dalam mengelola memorinya sendiri, dan perbedaan itu diam saja sampai akhirnya crash:
var
Book: IXLSWorkbook; // referensi interface: dilepas otomatis
Sheet: IXLSWorksheet;
BookX: TXLSXWorkbook; // objek biasa: Anda yang membebaskannya
SheetX: TXLSXWorksheet;
begin
// output BIFF8 .xls - tanpa Free; refcount interface yang mengelolanya
Book := TXLSWorkbook.Create;
Sheet := Book.Sheets.Add;
Sheet.Name := 'Report';
Sheet.Cells.Item[1, 1].Value := 'Generated without Excel';
Book.SaveAs('report.xls');
// output OOXML .xlsx - lifetime eksplisit
BookX := TXLSXWorkbook.Create;
try
SheetX := BookX.Sheets.Add('Report');
SheetX.Cells[1, 1].Value := 'Generated without Excel';
BookX.SaveAs('report.xlsx');
finally
BookX.Free;
end;
end;
Facade XLS di-reference-count melalui interface IXLSWorkbook. Deklarasikan variabelnya sebagai tipe interface dan jangan pernah memanggil Free padanya; jika objek yang sama disimpan dalam variabel objek biasa lalu dibebaskan sendiri, refcount akan membebaskannya untuk kedua kalinya. Facade XLSX adalah objek biasa yang menginginkan try..finally biasa pula. Pengalamatan sel berbasis 1 di kedua sisi, satu-satunya titik di mana keduanya sepakat. Koleksi sheet-nya tidak: Entries di sisi XLS berbasis 1, indexer Items di XLSX berbasis 0, dan kesalahan selisih satu itu tetap terkompilasi bersih ke arah mana pun Anda salah dan baru menampakkan diri saat runtime
Menulis Workbook Langsung ke Respons HTTP
Ekspor sisi server biasanya tidak punya alasan untuk menyentuh disk. File temp menuntut kebijakan pembersihan, saling bertabrakan saat request konkuren, dan meninggalkan data pelanggan tergeletak di volume yang tak terpikirkan untuk diaudit. Kedua facade menerima TStream melalui overload SaveAs-nya, sehingga workbook bisa langsung masuk ke respons:
Mem := TMemoryStream.Create;
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Data');
Sheet.Cells[1, 1].Value := 'Generated ' + DateTimeToStr(Now);
Book.SaveAs(Mem); // menulis dari posisi stream SAAT INI
Mem.Position := 0; // rewind sebelum menyerahkan stream
Response.ContentType :=
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
Response.ContentStream := Mem; // sekarang framework yang memiliki Mem
finally
Book.Free;
end;
Baris rewind itulah yang layak mendapat komentarnya. SaveAs(Stream) menulis dari posisi stream saat ini dan tidak pernah mundur ke posisi nol setelahnya. Lupakan Mem.Position := 0 dan klien akan menerima unduhan berukuran nol byte, atau Excel akan menyatakan filenya rusak. Ini adalah bug paling umum dalam kode workbook yang menghadap web, dan yang paling kejam, karena ia lolos begitu saja dari unit test mana pun yang hanya memastikan stream punya panjang bukan nol
Satu rutin pembuatan workbook bisa menjangkau setiap format pengiriman lain tanpa perlu restrukturisasi. SaveAsCSV menjawab permintaan "beri saja saya data mentahnya," SaveAsHTML menangani "masukkan ke halaman portal," SaveAsRTF memberi makan pipeline dokumen, dan SaveAsODS mencakup mandat OpenDocument, semuanya dengan overload file maupun stream. Satu rutin ekspor ditambah satu parameter format menggantikan apa yang dulu cenderung berupa empat makro COM terpisah. TXLSXHtmlExportOptions milik exporter HTML membawa title, kelas CSS, dan saklar fragment-atau-dokumen-utuh, yang membuat kasus portal tidak perlu repot-repot mengedit markup hasil ekspor dengan regex
Nilai Formula Tanpa Proses Excel untuk Menghitungnya
Di bawah otomatisasi COM, Excel merekalkulasi semuanya secara cuma-cuma, dan melepas COM diam-diam mencabut fasilitas itu. SaveAs menyimpan formula sebagai teks tanpa mengevaluasinya; angkanya baru muncul begitu Excel membuka file dan merekalkulasi, perilaku yang bisa disetel melalui RecalcOnSave dan CalculationMode pada facade XLS. Untuk file yang ditujukan kepada seseorang, itu justru tepat. Itu keliru untuk layanan yang harus memastikan sebuah total sebelum dikirim, dan keliru pula untuk ekspor CSV, yang menuliskan teks formula alih-alih hasilnya. Kedua kasus itu harus dievaluasi di server dengan mesin bawaan:
SheetX.Cells[1, 1].Value := 1200;
SheetX.Cells[2, 1].Value := 950;
SheetX.Cells[3, 1].Formula := 'SUM(A1:A2)'; // facade XLSX: tanpa awalan '='
Total := BookX.Calculate('SUM(A1:A2)'); // evaluasi di server, sekarang
if Total <> 2150 then
raise Exception.Create('reconciliation failed before delivery');
Konvensi facade menggigit lagi di sini. Sisi XLSX menetapkan ekspresi melalui Cell.Formula tanpa tanda sama dengan; sisi XLS menuliskannya melalui Cell.Value dengan awalan '='. Bawa kode dari satu sisi ke sisi lain tanpa perubahan dan konvensi yang keliru itu akan menyimpan string teks yang sekadar menyerupai formula, tanpa ada error yang menandainya. Ketika formula sebuah workbook perlu menjangkau logika bisnis Anda sendiri, callback OnUserFunction membiarkan mesin menyerahkan nama fungsi yang tidak dikenal ke kode Delphi saat evaluasi. Itulah pengganti native untuk add-in UDF yang cenderung bersembunyi di dalam spreadsheet yang justru menjadi asal-usul sistem otomatisasi COM
Sisi-Sisi Deployment yang Hanya Muncul di Server
Beberapa detail menentukan apakah rollout-nya berjalan mulus atau membingungkan, dan yang pertama adalah graf unit. Exporter dataset drag-and-drop TDataToXLS menarik masuk VCL Forms, Controls, dan Dialogs. Tidak masalah pada tool desktop; pada layanan console, ia menyeret seluruh VCL ikut serta di belakangnya. Unit inti lxHandle dan lxHandleX hanya membutuhkan Windows, Classes, SysUtils, dan Variants, sehingga layanan murni lebih baik menulis sendiri loop dataset-nya terhadap API inti ketimbang mengimpor komponen hanya demi kenyamanan
Kemudian ada threading. Instance workbook tidak thread-safe, tetapi juga tidak berbagi state global apa pun, sehingga pola yang bisa berskala adalah yang paling sederhana: satu objek workbook per job, atau per worker thread. Itu memberikan generasi laporan paralel, sesuatu yang tidak pernah bisa dilakukan oleh satu instance Excel yang dibagi bersama. Sebuah request handler yang membuat, mengisi, menyimpan, dan membebaskan workbook-nya sendiri tidak memerlukan lock sama sekali, dan radius ledakan sebuah kegagalan menyusut dari "instance Excel bersama macet untuk semua orang" menjadi "request yang satu ini memunculkan exception," sesuatu yang penanganan error yang sudah ada tahu persis cara mengatasinya
Penargetan format adalah yang terakhir. TXLSWorkbook.SaveAs menulis BIFF (xlExcel97) secara default, dan mendorong konten XLS ke .xlsx berjalan melalui jembatan SaveXLSWorkbookAsXLSX dengan fidelitas yang berkurang. Pilih facade sesuai format yang memang hendak Anda kirim, sejak tahap desain, alih-alih membangun dengan satu facade lalu mengonversi di ujung pipeline
Untuk separuh pemuatan data dari proyek penggantian yang umum, pola ekspor database-ke-workbook mencakup baik komponen maupun loop yang ditulis tangan, dan begitu jumlah baris mencapai enam digit, teknik performa workbook besar menjadi pembeda antara hitungan menit dan hitungan detik. Laporan yang dibangun dari layout yang dikelola designer dibahas dalam panduan pembuatan laporan dari template
HotXLS dirilis sebagai source code Object Pascal untuk Delphi dan C++Builder; edisi, lisensi, dan referensi API lengkap tersedia di halaman produk HotXLS Delphi Component