Als de enige taak van een server is om Excel-bestanden uit te zenden, hoort hij geen Excel te draaien. Office installeren op een build-agent of een rapportservice om het via COM-automatisering aan te sturen, is het verkeerde ontwerp, en dat is het al zo lang als de praktijk bestaat. Microsoft zegt het zelf, in richtlijnen die twintig jaar lang niet zijn verzacht: Office is niet gebouwd noch gelicentieerd om te worden geautomatiseerd vanuit een onbewaakt, server-side proces. Het juiste antwoord is de BIFF- en OOXML-bytes rechtstreeks te schrijven, zonder Excel ergens in beeld. Dat is de hele premisse van HotXLS, een native Object Pascal-bibliotheek die de spreadsheetformaten zelf leest en schrijft, zodat er geen desktoptoepassing is om te blijven hangen, te lekken of per zitplaats voor te betalen
Waarom EXCEL.EXE aansturen vanuit een service faalt
COM-automatisering bedient op afstand een desktopprogramma, en een desktopprogramma neemt stilzwijgend drie dingen aan die een Windows-service het niet kan geven: een geladen gebruikersprofiel, een interactief window station, en een mens die naar het scherm kijkt. Strip die weg en de fouten arriveren in een vorm die geen ontwikkelaarsmachine ooit reproduceert. Een bestandsherstelmelding, een add-infout of een licentieactiveringsdialoog opent op een desktop die niemand kan zien, en de automatiseringsaanroep die het veroorzaakte, keert nooit terug. De aanroeper krijgt uiteindelijk een timeout en sterft; de Excel-instantie vaak niet, en overleeft als een wees die bestandsvergrendelingen vasthoudt en de volgende run vergiftigt. Iedereen die elf verdwaalde EXCEL.EXE-processen onder een serviceaccount heeft zien opstapelen, kent de rest van dat verhaal
Het schaalverhaal is niet beter, zelfs als er niets crasht. Een Excel-instantie is een pipeline voor één werkmap tegelijk, elke eigenschapstoegang betaalt de kosten van cross-process COM-marshaling, en de machine die de code draait, draagt een Office-licentie waarvan de voorwaarden precies dit gebruik uitsluiten. De meeste teams stuiten op deze grenzen één storing per keer, en dat is ongeveer hoe "de COM-laag afschaffen" op een roadmap terechtkomt
Voordat die herschrijving begint, moet je één scopevraag beslechten, want die bepaalt hoeveel van het werk echt is. COM-code stelt bijna nooit alleen celwaarden in. Het roept Workbook.SaveAs aan met formaatconstanten, dwingt herberekening af, stuurt afdrukinstellingen, en grijpt soms naar het klembord. Loop de oude code door en noteer welke van die gedragingen daadwerkelijk in de uitvoer terechtkomen, want elk landt in een ander hoekje van een native bibliotheek, en een paar ervan (klembordinterop is het voor de hand liggende voorbeeld) hebben geen server-side betekenis en moeten worden weggelaten in plaats van overgezet
Twee native engines, twee eigendomsmodellen
HotXLS vervangt het Excel-proces door twee directe formaatimplementaties. Een BIFF8-recordstroomengine (TXLSWorkbook, unit lxHandle) behandelt .xls. Een OOXML-pakketschrijver (TXLSXWorkbook, unit lxHandleX) produceert .xlsx dat voldoet aan ECMA-376 / ISO/IEC 29500. Er is niets te registreren en niets te installeren op de server, en je kunt zoveel werkmappen tegelijk open houden als het geheugen toelaat
Wat mensen vroeg laat struikelen is dat de twee facades hun geheugen anders bezitten, en het verschil is stil totdat het crasht:
var
Book: IXLSWorkbook; // interfaceverwijzing: automatisch vrijgegeven
Sheet: IXLSWorksheet;
BookX: TXLSXWorkbook; // gewoon object: jij geeft het vrij
SheetX: TXLSXWorksheet;
begin
// BIFF8-.xls-uitvoer - geen Free; de interface-refcount bezit het
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-uitvoer - expliciete levensduur
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;
De XLS-facade is referentiegeteld via de IXLSWorkbook-interface. Declareer de variabele als het interfacetype en roep er nooit Free op aan; houd hetzelfde object vast in een gewone objectvariabele en geef het zelf vrij, en de refcount geeft het een tweede keer vrij. De XLSX-facade is een gewoon object dat een gewone try..finally wil. Celadressering is aan beide kanten 1-gebaseerd, wat de enige plek is waar ze het eens zijn. De bladverzamelingen niet: Entries aan de XLS-kant is 1-gebaseerd, de XLSX-Items-indexer is 0-gebaseerd, en die off-by-one compileert netjes hoe je het ook fout doet en toont zich pas tijdens runtime
Een werkmap rechtstreeks naar een HTTP-respons schrijven
Een server-side export heeft meestal geen reden om de schijf aan te raken. Tijdelijke bestanden vragen om een opruimbeleid, botsen bij gelijktijdige verzoeken, en laten klantdata achter op volumes waar niemand aan dacht ze te auditen. Beide facades nemen een TStream via hun SaveAs-overloads, zodat de werkmap rechtstreeks in de respons terecht kan komen:
Mem := TMemoryStream.Create;
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Data');
Sheet.Cells[1, 1].Value := 'Generated ' + DateTimeToStr(Now);
Book.SaveAs(Mem); // schrijft vanaf de HUIDIGE streampositie
Mem.Position := 0; // spoel terug voordat je de stream overhandigt
Response.ContentType :=
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
Response.ContentStream := Mem; // het framework bezit Mem nu
finally
Book.Free;
end;
De terugspoeling is de regel die zijn commentaar verdient. SaveAs(Stream) schrijft vanaf de huidige positie van de stream en zoekt daarna nooit terug naar nul. Vergeet Mem.Position := 0 en de client krijgt een download van nul bytes, of Excel meldt het bestand corrupt. Dit is de meest voorkomende bug in web-gerichte workbook-code, en de wreedste, omdat hij ongemerkt langs elke unit test glipt die alleen bevestigt dat de stream een lengte groter dan nul heeft
Eén workbook-bouwroutine bereikt elk ander afleverformaat zonder herstructurering. SaveAsCSV beantwoordt het verzoek "geef me gewoon de ruwe data", SaveAsHTML behandelt "zet het in een portaalpagina", SaveAsRTF voedt documentpipelines, en SaveAsODS dekt een OpenDocument-mandaat, allemaal met zowel bestands- als stream-overloads. Eén exportroutine plus een formaatparameter vervangt wat vroeger vier aparte COM-macro's plachten te zijn. De TXLSXHtmlExportOptions van de HTML-exporter draagt titel, CSS-klasse en een fragment-of-volledig-document-schakelaar, wat het portaalgeval buiten het regex-bewerken van geëxporteerde markup houdt
Formulewaarden zonder een Excel-proces om ze te berekenen
Onder COM-automatisering herberekende Excel alles gratis, en COM loslaten trekt dat stilletjes in. SaveAs slaat formules op als tekst zonder ze te evalueren; de getallen verschijnen pas zodra Excel het bestand opent en herberekent, gedrag dat de XLS-facade je laat afstemmen via RecalcOnSave en CalculationMode. Voor een bestand dat naar een mens gaat is dat precies goed. Het is fout voor een service die een totaal moet bevestigen voordat het wordt uitgeleverd, en fout voor CSV-export, die de formuletekst schrijft in plaats van het resultaat. Beide gevallen moeten op de server evalueren met de ingebouwde engine:
SheetX.Cells[1, 1].Value := 1200;
SheetX.Cells[2, 1].Value := 950;
SheetX.Cells[3, 1].Formula := 'SUM(A1:A2)'; // XLSX-facade: geen '='-prefix
Total := BookX.Calculate('SUM(A1:A2)'); // evalueer nu op de server
if Total <> 2150 then
raise Exception.Create('reconciliation failed before delivery');
De facadeconventie bijt hier opnieuw. De XLSX-kant wijst expressies toe via Cell.Formula zonder isgelijkteken; de XLS-kant schrijft ze via Cell.Value met een voorafgaand '='. Draag code ongewijzigd van de ene naar de andere over en de verkeerde conventie slaat een tekststring op die slechts op een formule lijkt, zonder fout om het te markeren. Wanneer de formules van een werkmap in je eigen bedrijfslogica moeten reiken, laat de callback OnUserFunction de engine onbekende functienamen tijdens evaluatie doorgeven aan Delphi-code. Dat is de native vervanging voor de UDF-add-ins die de neiging hebben zich te verstoppen in precies de spreadsheets waaromheen een COM-automatiseringssysteem is opgegroeid
Deploymentrandjes die pas op de server naar boven komen
Een paar details bepalen of de uitrol schoon verloopt of verwarrend, en het eerste is de unitgraaf. De drag-and-drop-dataset-exporter TDataToXLS trekt VCL Forms, Controls en Dialogs binnen. Onschuldig in een desktoptool; in een consoleservice sleept het de hele VCL erachteraan mee. De kernunits lxHandle en lxHandleX grijpen alleen naar Windows, Classes, SysUtils en Variants, dus een pure service kan beter zijn eigen dataset-lus tegen de kern-API schrijven dan de component voor het gemak te importeren
Dan is er threading. Workbook-instanties zijn niet thread-safe, maar ze delen ook geen globale status, dus het patroon dat schaalt is het simpelste: één workbook-object per taak, of per workerthread. Dat koopt parallelle rapportgeneratie, wat een enkele gedeelde Excel-instantie nooit kan. Een requesthandler die zijn eigen werkmap aanmaakt, vult, opslaat en vrijgeeft, heeft helemaal geen locks nodig, en de impactstraal van een fout krimpt van "de gedeelde Excel-instantie zit voor iedereen vast" naar "dit ene verzoek gooide een uitzondering," waar je bestaande foutafhandeling al mee weet om te gaan
Formaatkeuze is de laatste van het stel. TXLSWorkbook.SaveAs schrijft standaard BIFF (xlExcel97), en XLS-inhoud naar .xlsx duwen loopt via de SaveXLSWorkbookAsXLSX-brug met verminderde getrouwheid. Kies de facade op basis van het formaat dat je van plan bent uit te leveren, op ontwerptijd, in plaats van in het ene te bouwen en aan het einde van de pipeline te converteren
Voor de datalaadhelft van een typisch vervangingsproject dekken de database-naar-workbook-exportpatronen zowel de component als de handgeschreven lus, en zodra rijaantallen zescijferig worden, maken de technieken voor prestaties van grote werkmappen het verschil tussen minuten en seconden. Rapporten opgebouwd uit door ontwerpers onderhouden lay-outs worden behandeld in de walkthrough voor sjabloon-rapportgeneratie
HotXLS wordt geleverd als Object Pascal-broncode voor Delphi en C++Builder; edities, licenties en de volledige API-referentie staan op de productpagina van HotXLS Delphi Component