Teknik Makale

Delphi'de Office Otomasyonu Olmadan Excel Dosyası Üretme

Bir sunucunun tek işi Excel dosyası üretmekse, Excel çalıştırmakla hiçbir işi yoktur. Bir derleme aracısına ya da rapor hizmetine Office kurup onu COM otomasyonuyla sürmek yanlış tasarımdır ve bu uygulama var olduğundan beri yanlış tasarımdır. Bunu Microsoft kendisi söylüyor, üstelik yirmi yıldır yumuşamayan bir kılavuzda: Office, gözetimsiz bir sunucu tarafı süreçten otomatikleştirilmek üzere ne inşa edilmiştir ne de lisanslanmıştır. Doğru yanıt, tabloda hiç Excel olmadan BIFF ve OOXML baytlarını doğrudan yazmaktır. HotXLS kütüphanesinin tüm önermesi budur: elektronik tablo biçimlerini kendisi okuyup yazan yerel bir Object Pascal kütüphanesi, dolayısıyla takılacak, sızdıracak ya da kişi başı ücret ödenecek bir masaüstü uygulaması yok

Bir hizmetten EXCEL.EXE sürmek neden başarısız olur

COM otomasyonu bir masaüstü programını uzaktan kumanda eder ve bir masaüstü programı, bir Windows hizmetinin ona veremeyeceği üç şeyi sessizce varsayar: yüklenmiş bir kullanıcı profili, etkileşimli bir pencere istasyonu ve ekranı izleyen bir insan. Bunları çekip alın; başarısızlıklar hiçbir geliştirici makinesinin yeniden üretemediği bir biçimde gelir. Kimsenin göremediği bir masaüstünde bir dosya kurtarma istemi, bir eklenti hatası ya da bir lisans etkinleştirme iletişim kutusu açılır ve bunu tetikleyen otomasyon çağrısı hiç geri dönmez. Çağıran taraf sonunda zaman aşımına uğrayıp ölür; Excel örneği ise çoğu zaman ölmez, dosya kilitlerini tutan ve bir sonraki çalıştırmayı zehirleyen bir öksüz olarak kalır. Bir hizmet hesabının altında on bir başıboş EXCEL.EXE sürecinin biriktiğini görmüş olan herkes hikâyenin gerisini bilir

COM otomasyonuyla EXCEL.EXE süren ve gizli iletişim kutuları ile öksüz süreçlerin çağrıları tıkadığı bir Delphi hizmetini, BIFF8 ile OOXML çalışma kitabı baytlarını süreç içinde doğrudan yazan HotXLS ile karşılaştıran şema
COM otomasyonu bir masaüstü programının eksik varsayımlarını devralır, HotXLS ise sunucuya kurulacak hiçbir şey olmadan BIFF8 ve OOXML baytlarını doğrudan yazar

Hiçbir şey çökmese bile ölçekleme hikâyesi daha iyi değildir. Bir Excel örneği tek çalışma kitaplık bir hattır, her özellik erişimi süreçler arası COM sıralamasının bedelini öder ve kodu çalıştıran makine, koşulları tam da bu kullanımı dışlayan bir Office lisansı taşır. Çoğu ekip bu sınırlarla her seferinde bir kesinti üzerinden tanışır ki "COM katmanını emekliye ayır" maddesinin yol haritasına kabaca böyle girdiği söylenebilir

O yeniden yazım başlamadan önce bir kapsam sorusunu netleştirin, çünkü işin ne kadarının gerçek olduğuna o karar verir. COM kodu neredeyse hiçbir zaman yalnızca hücre değerleri ayarlamaz. Biçim sabitleriyle Workbook.SaveAs çağırır, yeniden hesaplamayı zorlar, yazdırma ayarlarını iter, bazen panoya uzanır. Eski kodu dolaşın ve bu davranışlardan hangilerinin gerçekten çıktıya yansıdığını not edin; çünkü her biri yerel bir kütüphanenin farklı bir köşesine düşer ve birkaçının (pano birlikte çalışması bunun en bariz örneğidir) sunucu tarafında hiçbir anlamı yoktur, taşınmak yerine düşürülmelidir

İki yerel motor, iki sahiplik modeli

HotXLS, Excel sürecini iki doğrudan biçim uygulamasıyla değiştirir. Bir BIFF8 kayıt akışı motoru (TXLSWorkbook, lxHandle birimi) .xls dosyalarını ele alır. Bir OOXML paket yazıcısı (TXLSXWorkbook, lxHandleX birimi) ECMA-376 / ISO/IEC 29500 uyumlu .xlsx üretir. Sunucuya kaydedilecek ve kurulacak hiçbir şey yoktur ve bellek elverdiği sürece aynı anda istediğiniz kadar çalışma kitabını açık tutabilirsiniz

İki HotXLS Delphi cephesini karşılaştıran şema: IXLSWorkbook arayüz başvuru sayımıyla kendiliğinden serbest bırakılan TXLSWorkbook ile bir try..finally bloğunda açık bir Free çağrısı isteyen düz nesne TXLSXWorkbook
XLS cephesi arayüz başvuru sayımıyla serbest bırakılır, XLSX cephesi ise açık bir Free ister ve sayfa koleksiyonları 1 tabanlı Entries ile 0 tabanlı Items arasında ayrışır

İnsanları erkenden tökezleten şey, iki cephenin belleğine farklı sahip olması ve bu farkın çökene kadar sessiz kalmasıdır:

var
  Book: IXLSWorkbook;          // arayüz başvurusu: kendiliğinden serbest bırakılır
  Sheet: IXLSWorksheet;
  BookX: TXLSXWorkbook;        // düz nesne: siz serbest bırakırsınız
  SheetX: TXLSXWorksheet;
begin
  // BIFF8 .xls çıktısı - Free yok; sahibi arayüz başvuru sayacıdır
  Book := TXLSWorkbook.Create;
  Sheet := Book.Sheets.Add;
  Sheet.Name := 'Report';
  Sheet.Cells.Item[1, 1].Value := 'Generated without Excel';
  Book.SaveAs('report.xls');

  // OOXML .xlsx çıktısı - açık yaşam süresi
  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;

XLS cephesi IXLSWorkbook arayüzü üzerinden başvuru sayımlıdır. Değişkeni arayüz türünde bildirin ve onun üzerinde asla Free çağırmayın; aynı nesneyi düz bir nesne değişkeninde tutup kendiniz serbest bırakırsanız, başvuru sayacı onu ikinci kez serbest bırakır. XLSX cephesi ise sıradan bir try..finally isteyen sıradan bir nesnedir. Hücre adresleme her iki tarafta da 1 tabanlıdır ki ikisinin anlaştığı tek yer burasıdır. Sayfa koleksiyonları anlaşmaz: XLS tarafındaki Entries 1 tabanlı, XLSX tarafındaki Items dizinleyicisi 0 tabanlıdır ve bu bir fark, hangi yönde yanlış yaparsanız yapın temiz derlenir ve kendini yalnızca çalışma zamanında gösterir

Bir çalışma kitabını doğrudan HTTP yanıtına yazmak

Sunucu tarafındaki bir dışa aktarmanın diske dokunmak için genellikle hiçbir nedeni yoktur. Geçici dosyalar bir temizlik politikası ister, eşzamanlı isteklerde çakışır ve müşteri verisini kimsenin denetlemeyi düşünmediği birimlerde bırakır. Her iki cephe de SaveAs aşırı yüklemeleriyle bir TStream alır, dolayısıyla çalışma kitabı doğrudan yanıta gidebilir:

Mem := TMemoryStream.Create;
Book := TXLSXWorkbook.Create;
try
  Sheet := Book.Sheets.Add('Data');
  Sheet.Cells[1, 1].Value := 'Generated ' + DateTimeToStr(Now);
  Book.SaveAs(Mem);          // GEÇERLİ akış konumundan yazar
  Mem.Position := 0;         // akışı devretmeden önce başa sar
  Response.ContentType :=
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
  Response.ContentStream := Mem;   // artık Mem sahibi çerçevedir
finally
  Book.Free;
end;

Açıklamasını hak eden satır, başa sarma satırıdır. SaveAs(Stream) akışın geçerli konumundan yazar ve sonrasında asla sıfıra geri aramaz. Mem.Position := 0 satırını unutun; istemci sıfır baytlık bir indirme alır ya da Excel dosyaya bozuk der. Bu, web cepheli çalışma kitabı kodundaki en yaygın ve en zalim hatadır, çünkü yalnızca akışın uzunluğunun sıfırdan farklı olduğunu doğrulayan her birim testinin yanından süzülüp geçer

Tek bir çalışma kitabı kurma yordamı, yapıyı değiştirmeden diğer bütün teslim biçimlerine ulaşır. SaveAsCSV, "bana sadece ham veriyi ver" isteğini yanıtlar, SaveAsHTML "bunu bir portal sayfasına bırak" durumunu karşılar, SaveAsRTF belge hatlarını besler ve SaveAsODS bir OpenDocument zorunluluğunu kapsar; hepsi hem dosya hem akış aşırı yüklemeleriyle gelir. Tek bir dışa aktarma yordamı artı bir biçim parametresi, eskiden dört ayrı COM makrosu olan şeyin yerini alır. HTML dışa aktarıcısının TXLSXHtmlExportOptions yapısı başlık, CSS sınıfı ve parça mı tam belge mi anahtarını taşır ki bu da portal durumunu, dışa aktarılmış işaretlemeyi düzenli ifadeyle düzenleme işinden uzak tutar

Bir HotXLS çalışma kitabını TMemoryStream içine kaydeden, Mem.Position değerini sıfıra saran ve akışı HTTP yanıtına devreden bir Delphi istek işleyicisinin şeması, yanında CSV, HTML, RTF ve ODS dışa aktarıcıları
Bir TMemoryStream içine kaydedip devretmeden önce başa sarmak çalışma kitabı baytlarını doğrudan istemciye gönderir ve tek bir dışa aktarma yordamı CSV, HTML, RTF ve ODS yazıcılarını kapsar

Hesaplayacak bir Excel süreci olmadan formül değerleri

COM otomasyonu altında Excel her şeyi bedavaya yeniden hesaplıyordu ve COM katmanını düşürmek bunu sessizce geri alır. SaveAs formülleri değerlendirmeden metin olarak saklar; sayılar ancak Excel dosyayı açıp yeniden hesapladığında belirir ve XLS cephesi bu davranışı RecalcOnSave ile CalculationMode üzerinden ayarlamanıza izin verir. Bir insana gidecek bir dosya için bu tam olarak doğrudur. Bir toplamı göndermeden önce doğrulamak zorunda olan bir hizmet için yanlıştır ve formülün sonucunu değil metnini yazan CSV dışa aktarımı için de yanlıştır. Her iki durumda da değerlendirmenin sunucuda yerleşik motorla yapılması gerekir:

SheetX.Cells[1, 1].Value := 1200;
SheetX.Cells[2, 1].Value := 950;
SheetX.Cells[3, 1].Formula := 'SUM(A1:A2)';   // XLSX cephesi: '=' öneki yok
Total := BookX.Calculate('SUM(A1:A2)');       // hemen sunucuda değerlendir
if Total <> 2150 then
  raise Exception.Create('reconciliation failed before delivery');

Cephe geleneği burada yine ısırır. XLSX tarafı ifadeleri eşittir işareti olmadan Cell.Formula üzerinden atar; XLS tarafı ise onları başta bir '=' ile Cell.Value üzerinden yazar. Kodu birinden diğerine değiştirmeden taşıyın; yanlış gelenek, yalnızca formüle benzeyen bir metin dizesi saklar ve bunu işaretleyecek hiçbir hata olmaz. Bir çalışma kitabının formüllerinin kendi iş mantığınıza uzanması gerektiğinde, OnUserFunction geri çağrısı motorun bilinmeyen işlev adlarını değerlendirme anında Delphi koduna devretmesini sağlar. Bu, bir COM otomasyon sisteminin etrafında büyüdüğü elektronik tabloların içinde saklanma eğiliminde olan UDF eklentilerinin yerel karşılığıdır

Yalnızca sunucuda yüzeye çıkan dağıtım köşeleri

Birkaç ayrıntı yayılımın temiz mi yoksa kafa karıştırıcı mı olacağına karar verir ve ilki birim çizgesidir. Sürükle bırak veri kümesi dışa aktarıcısı TDataToXLS, VCL tarafındaki Forms, Controls ve Dialogs birimlerini içeri çeker. Bir masaüstü aracında zararsızdır; bir konsol hizmetinde ise bütün VCL yapısını peşinden sürükler. Çekirdek birimler lxHandle ve lxHandleX yalnızca Windows, Classes, SysUtils ve Variants birimlerine uzanır, dolayısıyla saf bir hizmet için kolaylık olsun diye bileşeni içeri almaktansa çekirdek API karşısında kendi veri kümesi döngüsünü yazmak daha iyidir

Sonra iş parçacığı meselesi var. Çalışma kitabı örnekleri iş parçacığı güvenli değildir, ama küresel durum da paylaşmazlar; dolayısıyla ölçeklenen desen en basit olanıdır: iş başına ya da çalışan iş parçacığı başına bir çalışma kitabı nesnesi. Bu size paralel rapor üretimi kazandırır ki tek bir paylaşımlı Excel örneği bunu hiçbir zaman yapamaz. Kendi çalışma kitabını oluşturan, dolduran, kaydeden ve serbest bırakan bir istek işleyicisi hiç kilit gerektirmez ve bir başarısızlığın patlama yarıçapı "paylaşımlı Excel örneği herkes için sıkıştı" düzeyinden "bu tek istek bir istisna yükseltti" düzeyine iner ki var olan hata işleyiciniz bununla ne yapacağını zaten bilir

Sonuncusu biçim hedeflemedir. TXLSWorkbook.SaveAs varsayılan olarak BIFF (xlExcel97) yazar ve XLS içeriğini .xlsx biçimine itmek, düşük sadakatle SaveXLSWorkbookAsXLSX köprüsünden geçer. Cepheyi, birinde kurup hattın sonunda dönüştürmek yerine, göndermeyi düşündüğünüz biçime göre tasarım anında seçin

Tipik bir değiştirme projesinin veri yükleme yarısı için veritabanından çalışma kitabına aktarma desenleri hem bileşeni hem elle yazılmış döngüyü kapsar ve satır sayıları altı haneye ulaştığında büyük çalışma kitabı başarım teknikleri, dakikalarla saniyeler arasındaki farkı belirler. Tasarımcı elinden çıkma düzenlerden kurulan raporlar şablon rapor üretimi anlatımında ele alınmaktadır

HotXLS, Delphi ve C++Builder için Object Pascal kaynak kodu olarak gelir; sürümler, lisanslama ve eksiksiz API başvurusu HotXLS Delphi Bileşeni ürün sayfasında yer alır