Hvis en servers eneste jobb er å produsere Excel-filer, har den ingen grunn til å kjøre Excel. Å installere Office på en build-agent eller en rapporttjeneste for å styre det gjennom COM-automatisering er feil design, og det har vært feil design så lenge praksisen har eksistert. Microsoft sier det selv, i veiledning som ikke har blitt mykere på tjue år: Office er verken bygget eller lisensiert for å automatiseres fra en ubetjent, serverside-prosess. Det riktige svaret er å skrive BIFF- og OOXML-bytene direkte, uten Excel i bildet i det hele tatt. Det er hele premisset for HotXLS, et nativt Object Pascal-bibliotek som selv leser og skriver regnearkformatene, slik at det ikke finnes noen skrivebordsapplikasjon å henge, lekke eller betale per lisens for
Hvorfor det å styre EXCEL.EXE fra en tjeneste mislykkes
COM-automatisering fjernstyrer et skrivebordsprogram, og et skrivebordsprogram forutsetter stilltiende tre ting som en Windows-tjeneste ikke kan gi det: en innlastet brukerprofil, en interaktiv vindusstasjon, og et menneske som ser på skjermen. Fjern disse forutsetningene, og feilene dukker opp i en form ingen utviklermaskin noensinne gjengir. En dialogboks for filgjenoppretting, en tillegg-feil eller en lisensaktiveringsdialog åpner seg på et skrivebord ingen kan se, og automatiseringskallet som utløste den, kommer aldri tilbake. Den kallende prosessen får til slutt tidsavbrudd og dør; Excel-instansen gjør det ofte ikke, og overlever som en foreldreløs prosess som holder filås og forgifter neste kjøring. Alle som har sett elleve gjenglemte EXCEL.EXE-prosesser hope seg opp under en tjenestekonto, kjenner resten av historien
Skaleringshistorien er ikke bedre selv når ingenting krasjer. En Excel-instans er en pipeline for én arbeidsbok om gangen, hver egenskapstilgang betaler kostnaden for COM-marshaling på tvers av prosesser, og maskinen som kjører koden bærer en Office-lisens hvis vilkår utelukker nettopp denne bruken. De fleste team møter disse begrensningene ett driftsavbrudd om gangen, og det er omtrent slik "pensjonere COM-laget" ender opp på et veikart
Før den omskrivningen starter, avklar ett spørsmål om omfang, for det avgjør hvor mye av arbeidet som er reelt. COM-kode setter nesten aldri bare celleverdier. Den kaller Workbook.SaveAs med formatkonstanter, tvinger frem omregning, sender ut utskriftsoppsett, og griper noen ganger til utklippstavlen. Gå gjennom den gamle koden og noter hvilke av disse atferdene som faktisk havner i utdataene, siden hver av dem lander i et forskjellig hjørne av et nativt bibliotek, og et par av dem (utklippstavle-interop er det opplagte eksempelet) har ingen serverside-mening og bør droppes fremfor å porteres
To native motorer, to eiermodeller
HotXLS bytter ut Excel-prosessen med to direkte formatimplementasjoner. En BIFF8-postrømningsmotor (TXLSWorkbook, enheten lxHandle) håndterer .xls. En OOXML-pakkeskriver (TXLSXWorkbook, enheten lxHandleX) produserer .xlsx som følger ECMA-376 / ISO/IEC 29500. Det er ingenting å registrere og ingenting å installere på serveren, og du kan holde like mange arbeidsbøker åpne samtidig som minnet tillater
Det som lurer folk tidlig, er at de to fasadene eier minnet sitt på ulike måter, og forskjellen er stille helt til den krasjer:
var
Book: IXLSWorkbook; // interface-referanse: frigjøres automatisk
Sheet: IXLSWorksheet;
BookX: TXLSXWorkbook; // vanlig objekt: du frigjør det selv
SheetX: TXLSXWorksheet;
begin
// BIFF8 .xls-utdata - ingen Free; interface-referansetellingen eier det
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-utdata - eksplisitt levetid
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-fasaden er referansetalt gjennom IXLSWorkbook-grensesnittet. Deklarer variabelen som grensesnitt-typen og kall aldri Free på den; hold det samme objektet i en vanlig objektvariabel og frigjør det selv, så frigjør referansetellingen det en gang til. XLSX-fasaden er et ordinært objekt som vil ha en ordinær try..finally. Celleadressering er 1-basert på begge sider, som er det ene stedet de to er enige. Arksamlingene er det ikke: Entries på XLS-siden er 1-basert, XLSX-ens Items-indekser er 0-basert, og den av-med-én-feilen kompilerer rent uansett hvilken vei du gjør den feil på, og viser seg først ved kjøretid
Å skrive en arbeidsbok direkte inn i et HTTP-svar
En serverside-eksport har som regel ingen grunn til å røre disken. Midlertidige filer krever en opprydningspolicy, kolliderer under samtidige forespørsler, og lar kundedata bli liggende på volumer ingen har tenkt å revidere. Begge fasadene tar imot en TStream gjennom sine SaveAs-overlastinger, så arbeidsboken kan gå rett inn i svaret:
Mem := TMemoryStream.Create;
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Data');
Sheet.Cells[1, 1].Value := 'Generated ' + DateTimeToStr(Now);
Book.SaveAs(Mem); // skriver fra GJELDENDE strømposisjon
Mem.Position := 0; // spol tilbake før strømmen overleveres
Response.ContentType :=
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
Response.ContentStream := Mem; // rammeverket eier nå Mem
finally
Book.Free;
end;
Tilbakespolingen er linjen som fortjener kommentaren sin. SaveAs(Stream) skriver fra strømmens gjeldende posisjon og søker aldri tilbake til null etterpå. Glem Mem.Position := 0, og klienten får en nedlasting på null byte, eller Excel erklærer filen ødelagt. Dette er den vanligste feilen i webvendt arbeidsbok-kode, og den grusomste, fordi den glir forbi enhver enhetstest som bare bekrefter at strømmen har en lengde ulik null
Én arbeidsbok-byggende rutine når frem til alle andre leveringsformater uten omstrukturering. SaveAsCSV svarer på forespørselen "bare gi meg rådataene", SaveAsHTML håndterer "dropp det inn i en portalside," SaveAsRTF mater dokumentpipelines, og SaveAsODS dekker et OpenDocument-krav, alt med både fil- og strømoverlastinger. Én enkelt eksportrutine pluss en formatparameter erstatter det som pleide å være fire separate COM-makroer. HTML-eksportørens TXLSXHtmlExportOptions bærer tittel, CSS-klasse, og en bryter for fragment-eller-helt-dokument, som holder portal-tilfellet unna jobben med å regex-redigere eksportert markup
Formelverdier uten en Excel-prosess til å beregne dem
Under COM-automatisering regnet Excel om alt gratis, og å droppe COM trekker stille tilbake den fordelen. SaveAs lagrer formler som tekst uten å evaluere dem; tallene dukker først opp når Excel åpner filen og regner om, en atferd XLS-fasaden lar deg justere gjennom RecalcOnSave og CalculationMode. For en fil som skal til et menneske, er det helt riktig. Det er feil for en tjeneste som må bekrefte en sum før den sendes ut, og feil for CSV-eksport, som skriver formelteksten fremfor resultatet. Begge tilfellene må evalueres på serveren med den innebygde motoren:
SheetX.Cells[1, 1].Value := 1200;
SheetX.Cells[2, 1].Value := 950;
SheetX.Cells[3, 1].Formula := 'SUM(A1:A2)'; // XLSX-fasade: ingen '='-prefiks
Total := BookX.Calculate('SUM(A1:A2)'); // evaluer på serveren, nå
if Total <> 2150 then
raise Exception.Create('reconciliation failed before delivery');
Fasadekonvensjonen biter til igjen her. XLSX-siden tilordner uttrykk gjennom Cell.Formula uten likhetstegn; XLS-siden skriver dem gjennom Cell.Value med et innledende '='. Bær kode fra den ene til den andre uendret, og den feile konvensjonen lagrer en tekststreng som bare ligner på en formel, uten noen feil som flagger det. Når en arbeidsboks formler må nå inn i din egen forretningslogikk, lar OnUserFunction-tilbakekallet motoren gi ukjente funksjonsnavn videre til Delphi-kode ved evalueringstidspunktet. Det er den native erstatningen for UDF-tilleggene som gjerne gjemmer seg inne i akkurat de regnearkene et COM-automatiseringssystem vokste opp rundt
Driftsetting-detaljer som bare dukker opp på serveren
Noen få detaljer avgjør om utrullingen blir ryddig eller forvirrende, og den første er enhetsgrafen. Dra-og-slipp-datasetteksportøren TDataToXLS drar med seg VCL Forms, Controls og Dialogs. Ufarlig i et skrivebordsverktøy; i en konsolltjeneste sleper den med hele VCL-en bak seg. Kjerneenhetene lxHandle og lxHandleX rekker bare etter Windows, Classes, SysUtils og Variants, så en ren tjeneste er bedre tjent med å skrive sin egen datasettløkke mot kjerne-API-et enn å importere komponenten for bekvemmelighets skyld
Så er det trådsikkerhet. Arbeidsbok-instanser er ikke trådsikre, men de deler heller ingen global tilstand, så mønsteret som skalerer, er det enkleste: ett arbeidsbok-objekt per jobb, eller per arbeidertråd. Det gir parallell rapportgenerering, noe en enkelt delt Excel-instans aldri kan gjøre. En forespørselshåndterer som oppretter, fyller, lagrer og frigjør sin egen arbeidsbok, trenger ingen låser i det hele tatt, og skadeomfanget av en feil krymper fra "den delte Excel-instansen er låst fast for alle" til "denne ene forespørselen kastet et unntak," noe den eksisterende feilhåndteringen din allerede vet hva den skal gjøre med
Formatvalg er det siste av dem. TXLSWorkbook.SaveAs skriver BIFF (xlExcel97) som standard, og å presse XLS-innhold inn i .xlsx går gjennom SaveXLSWorkbookAsXLSX-broen med redusert nøyaktighet. Velg fasaden ut fra formatet du har tenkt å levere, ved designtidspunktet, fremfor å bygge i den ene og konvertere på slutten av pipelinen
For datalastings-halvparten av et typisk erstatningsprosjekt dekker eksportmønstrene fra database til arbeidsbok både komponenten og den håndskrevne løkken, og når radantallet når seks sifre, blir ytelsesteknikkene for store arbeidsbøker forskjellen mellom minutter og sekunder. Rapporter bygget fra designer-vedlikeholdte oppsett er dekket i gjennomgangen av malbasert rapportgenerering
HotXLS leveres som Object Pascal-kildekode for Delphi og C++Builder; utgaver, lisensiering og den fulle API-referansen finner du på HotXLS Delphi Component-produktsiden