Ha egy kiszolgáló egyetlen dolga Excel fájlok kibocsátása, semmi keresnivalója nincs az Excel futtatásának. Az Office telepítése egy fordítóügynökre vagy jelentésszolgáltatásra azért, hogy COM automatizáláson át vezérelje, rossz tervezés, és pontosan addig volt rossz tervezés, amíg ez a gyakorlat létezik. Ezt maga a Microsoft is így mondja, olyan útmutatásban, amely húsz év alatt sem puhult: az Office-t nem arra építették és nem arra licencelik, hogy felügyelet nélküli, kiszolgálóoldali folyamatból automatizálják. A helyes válasz a BIFF és OOXML bájtok közvetlen kiírása, mindenféle Excel nélkül. Ez a HotXLS teljes előfeltevése, azé a natív Object Pascal könyvtáré, amely maga olvassa és írja a táblázatformátumokat, így nincs asztali alkalmazás, amely megakadhat, szivároghat vagy férőhelyenként fizettethet
Miért bukik meg az EXCEL.EXE vezérlése egy szolgáltatásból
A COM automatizálás egy asztali programot távirányít, egy asztali program pedig csendben feltételez három dolgot, amelyet egy Windows szolgáltatás nem tud átadni neki: betöltött felhasználói profilt, interaktív ablakállomást és egy embert, aki a képernyőt nézi. Vegye el ezeket, és a hibák olyan formában érkeznek, amelyet egyetlen fejlesztői gép sem reprodukál. Egy fájl-helyreállítási kérdés, egy bővítményhiba vagy egy licencaktiválási párbeszédpanel nyílik meg egy olyan asztalon, amelyet senki nem lát, az azt kiváltó automatizálási hívás pedig soha nem tér vissza. A hívó végül időtúllépéssel elhal; az Excel példány gyakran nem, hanem árvaként él tovább, fájlzárolásokat tart, és megmérgezi a következő futást. Aki már látott tizenegy elkóborolt EXCEL.EXE folyamatot feltorlódni egy szolgáltatásfiók alatt, ismeri a történet többi részét
A skálázódás története sem jobb akkor, ha semmi nem omlik össze. Egy Excel példány egyetlen munkafüzetet kezelő futószalag, minden tulajdonság-hozzáférés megfizeti a folyamatok közötti COM csomagolás árát, a kódot futtató gép pedig olyan Office licencet hordoz, amelynek feltételei épp ezt a felhasználást zárják ki. A legtöbb csapat egy-egy üzemzavaronként ismerkedik meg ezekkel a korlátokkal, és nagyjából így kerül a „COM réteg nyugdíjazása” egy ütemtervre
Mielőtt ez az újraírás elindul, tisztázzon egy hatókörkérdést, mert az dönti el, mennyi a munkából a valódi. A COM kód szinte soha nem csak cellaértékeket állít be. Meghívja a Workbook.SaveAs metódust formátumkonstansokkal, újraszámolást kényszerít, nyomtatási beállítást tol, néha a vágólaphoz nyúl. Járja végig a régi kódot, és írja le, e viselkedések közül melyik jelenik meg ténylegesen a kimenetben, hiszen mindegyik egy natív könyvtár más sarkába érkezik, és néhányuknak (a vágólapos átjárás a nyilvánvaló) semmilyen kiszolgálóoldali jelentése nincs, ezért átvitel helyett el kell hagyni őket
Két natív motor, két birtoklási modell
A HotXLS az Excel folyamatot két közvetlen formátummegvalósításra cseréli. Egy BIFF8 rekordfolyam-motor (TXLSWorkbook, lxHandle unit) kezeli az .xls fájlokat. Egy OOXML csomagíró (TXLSXWorkbook, lxHandleX unit) állítja elő az ECMA-376 / ISO/IEC 29500 szerinti .xlsx fájlokat. Semmit nem kell regisztrálni és semmit nem kell telepíteni a kiszolgálóra, és annyi munkafüzetet tarthat egyszerre nyitva, amennyit a memória enged
Ami korán megbuktatja az embereket, az az, hogy a két homlokzat másként birtokolja a memóriáját, és a különbség egészen az összeomlásig néma:
var
Book: IXLSWorkbook; // felülethivatkozás: automatikusan felszabadul
Sheet: IXLSWorksheet;
BookX: TXLSXWorkbook; // sima objektum: Ön szabadítja fel
SheetX: TXLSXWorksheet;
begin
// BIFF8 .xls kimenet - nincs Free; a felület hivatkozásszámlálása birtokolja
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 kimenet - kifejezett élettartam
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;
Az XLS homlokzat az IXLSWorkbook felületen át hivatkozásszámlált. A változót a felület típusaként deklarálja, és soha ne hívjon rá Free metódust; ha ugyanazt az objektumot sima objektumváltozóban tartja és maga szabadítja fel, a hivatkozásszámláló másodszor is felszabadítja. Az XLSX homlokzat közönséges objektum, amely közönséges try..finally szerkezetet kíván. A cellacímzés mindkét oldalon 1-alapú, ez az egyetlen pont, ahol a kettő egyetért. A lapgyűjtemények nem: az XLS oldali Entries 1-alapú, az XLSX Items indexelő 0-alapú, és ez az eggyel elcsúszás akárhogy is rontja el, tisztán lefordul, és csak futásidőben mutatja meg magát
Munkafüzet írása egyenesen egy HTTP válaszba
Egy kiszolgálóoldali exportnak rendszerint semmi oka a lemezhez nyúlni. Az ideiglenes fájlok takarítási szabályzatot követelnek, párhuzamos kérések alatt ütköznek, és olyan köteteken hagynak ügyféladatot, amelyek ellenőrzésére senki nem gondolt. Mindkét homlokzat elfogad TStream objektumot a SaveAs túlterhelésein át, így a munkafüzet egyenesen a válaszba kerülhet:
Mem := TMemoryStream.Create;
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Data');
Sheet.Cells[1, 1].Value := 'Generated ' + DateTimeToStr(Now);
Book.SaveAs(Mem); // az AKTUÁLIS adatfolyam-pozíciótól ír
Mem.Position := 0; // tekerje vissza az adatfolyam átadása előtt
Response.ContentType :=
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
Response.ContentStream := Mem; // a Mem mostantól a keretrendszeré
finally
Book.Free;
end;
A visszatekerés az a sor, amely kiérdemli a megjegyzését. A SaveAs(Stream) az adatfolyam aktuális pozíciójától ír, és utána soha nem keres vissza nullára. Felejtse el a Mem.Position := 0 sort, és az ügyfél nulla bájtos letöltést kap, vagy az Excel sérültnek mondja a fájlt. Ez a leggyakoribb hiba a webre néző munkafüzetkódban, és a legkegyetlenebb, mert átvitorlázik minden olyan egységteszten, amely csak azt állítja, hogy az adatfolyam hossza nem nulla
Egyetlen munkafüzetépítő rutin átrendezés nélkül elér minden más kézbesítési formátumot. A SaveAsCSV a „csak add ide a nyers adatot” kérésre válaszol, a SaveAsHTML a „dobd be egy portáloldalra” esetet kezeli, a SaveAsRTF dokumentumfolyamatokat táplál, a SaveAsODS pedig egy OpenDocument előírást fed le, mindegyik fájl- és adatfolyamos túlterheléssel. Egyetlen exportrutin plusz egy formátumparaméter váltja ki azt, ami korábban négy külön COM makró szokott lenni. A HTML exportáló TXLSXHtmlExportOptions objektuma címet, CSS osztályt és egy töredék-vagy-teljes-dokumentum kapcsolót hordoz, ami kiveszi a portálesetet az exportált jelölés reguláris kifejezéses szerkesztgetésének üzletágából
Képletértékek anélkül, hogy egy Excel folyamat számolná ki őket
COM automatizálás alatt az Excel ingyen újraszámolt mindent, és a COM elhagyása ezt csendben visszavonja. A SaveAs kiértékelés nélkül, szövegként tárolja a képleteket; a számok csak akkor jelennek meg, amikor az Excel megnyitja a fájlt és újraszámol, ezt a viselkedést pedig az XLS homlokzat a RecalcOnSave és a CalculationMode tagokkal engedi hangolni. Egy embernek szánt fájlnál ez pontosan helyes. Helytelen viszont egy olyan szolgáltatásnál, amelynek szállítás előtt igazolnia kell egy végösszeget, és helytelen a CSV exportnál, amely a képlet szövegét írja ki az eredménye helyett. Mindkét esetben a kiszolgálón kell kiértékelni a beépített motorral:
SheetX.Cells[1, 1].Value := 1200;
SheetX.Cells[2, 1].Value := 950;
SheetX.Cells[3, 1].Formula := 'SUM(A1:A2)'; // XLSX homlokzat: nincs '=' előtag
Total := BookX.Calculate('SUM(A1:A2)'); // kiértékelés a kiszolgálón, most
if Total <> 2150 then
raise Exception.Create('reconciliation failed before delivery');
A homlokzategyezmény itt megint harap. Az XLSX oldal a Cell.Formula tagon át, egyenlőségjel nélkül adja meg a kifejezéseket; az XLS oldal a Cell.Value tagon át írja őket, kezdő '=' jellel. Vigye át a kódot változatlanul az egyikről a másikra, és a rossz egyezmény olyan szöveges karakterláncot tárol, amely pusztán képletre hasonlít, minden jelző hiba nélkül. Amikor egy munkafüzet képleteinek a saját üzleti logikájába kell nyúlniuk, az OnUserFunction visszahívás engedi, hogy a motor kiértékeléskor Delphi kódnak adja át az ismeretlen függvényneveket. Ez a natív helyettesítője azoknak a UDF bővítményeknek, amelyek épp azokban a táblázatokban szoktak megbújni, amelyek köré egy COM automatizálású rendszer felnőtt
Telepítési peremek, amelyek csak a kiszolgálón bukkannak fel
Néhány részlet dönti el, hogy a bevezetés tiszta vagy rejtélyes lesz, és az első a unitgráf. A beejthető adatkészlet-exportáló TDataToXLS behúzza a VCL Forms, Controls és Dialogs egységeit. Egy asztali eszközben ez ártalmatlan; egy konzolos szolgáltatásban maga után vonszolja az egész VCL-t. Az lxHandle és lxHandleX magegységek csak a Windows, Classes, SysUtils és Variants egységekhez nyúlnak, ezért egy tiszta szolgáltatás jobban jár, ha saját adatkészlet-ciklust ír a mag API ellen, mintha kényelmi okból importálná a komponenst
Aztán ott a szálkezelés. A munkafüzet-példányok nem szálbiztosak, de közös globális állapotuk sincs, így a skálázódó minta a legegyszerűbb: feladatonként vagy munkaszálanként egy munkafüzet-objektum. Ez párhuzamos jelentéskészítést vesz, amit egyetlen közös Excel példány sosem tud. Az a kéréskezelő, amely a saját munkafüzetét hozza létre, tölti fel, menti és szabadítja fel, semmilyen zárat nem igényel, egy hiba robbanási sugara pedig a „a közös Excel példány mindenki számára beszorult” szintről a „ez az egy kérés kivételt váltott ki” szintre zsugorodik, amivel a meglévő hibakezelése már tudja, mit kezdjen
A formátum célzása az utolsó közülük. A TXLSWorkbook.SaveAs alapértelmezés szerint BIFF (xlExcel97) formátumot ír, az XLS tartalom .xlsx felé tolása pedig a SaveXLSWorkbookAsXLSX hídon át, csökkentett hűséggel megy. A homlokzatot a szállítani kívánt formátum szerint válassza, tervezéskor, ne az egyikben építsen, hogy a folyamat végén átalakítson
Egy tipikus kiváltó projekt adatbetöltő feléhez az adatbázisból munkafüzetbe vezető exportminták mind a komponenst, mind a kézzel írt ciklust lefedik, és amint a sorszám hatjegyűvé válik, a nagy munkafüzetek teljesítménytechnikái jelentik a percek és a másodpercek közötti különbséget. A tervezők által karbantartott elrendezésekből épülő jelentéseket a sablonalapú jelentéskészítés végigvezetése tárgyalja
A HotXLS Object Pascal forrásként érkezik Delphihez és C++Builderhez; a kiadások, a licencelés és a teljes API hivatkozás a HotXLS Delphi Component termékoldalán található