Τεχνικό Άρθρο

Δημιουργία αρχείων Excel στο Delphi χωρίς Office Automation

Αν η μοναδική δουλειά ενός διακομιστή είναι να εκδίδει αρχεία Excel, δεν έχει καμία δουλειά να τρέχει το Excel. Η εγκατάσταση του Office σε έναν build agent ή μια υπηρεσία αναφορών για να το οδηγήσετε μέσω COM automation είναι λάθος σχεδιασμός, και ήταν λάθος σχεδιασμός από τότε που υπάρχει αυτή η πρακτική. Η ίδια η Microsoft το λέει, σε οδηγίες που δεν έχουν μαλακώσει επί είκοσι χρόνια: το Office δεν είναι ούτε χτισμένο ούτε αδειοδοτημένο για να αυτοματοποιείται από μια μη επιτηρούμενη διεργασία στην πλευρά του διακομιστή. Η σωστή απάντηση είναι να γράφετε τα bytes BIFF και OOXML απευθείας, χωρίς καθόλου Excel στην εικόνα. Αυτή είναι ολόκληρη η βασική ιδέα του HotXLS, μιας εγγενούς βιβλιοθήκης Object Pascal που διαβάζει και γράφει η ίδια τις μορφές υπολογιστικών φύλλων, οπότε δεν υπάρχει καμία εφαρμογή desktop να κρεμάσει, να διαρρεύσει, ή να πληρώνετε ανά θέση γι' αυτήν

Γιατί αποτυγχάνει η οδήγηση του EXCEL.EXE από μια υπηρεσία

Το COM automation ελέγχει εξ αποστάσεως ένα πρόγραμμα desktop, και ένα πρόγραμμα desktop υποθέτει σιωπηλά τρία πράγματα που μια υπηρεσία Windows δεν μπορεί να του παραδώσει: ένα φορτωμένο προφίλ χρήστη, έναν διαδραστικό window station, και έναν άνθρωπο που παρακολουθεί την οθόνη. Αφαιρέστε αυτά και οι αστοχίες φτάνουν σε μορφή που καμία μηχανή προγραμματιστή δεν αναπαράγει ποτέ. Ένα prompt ανάκτησης αρχείου, ένα σφάλμα πρόσθετου, ή ένα παράθυρο διαλόγου ενεργοποίησης άδειας ανοίγει σε ένα desktop που κανείς δεν μπορεί να δει, και η κλήση automation που το ενεργοποίησε δεν επιστρέφει ποτέ. Ο καλών τελικά λήγει και πεθαίνει· η παρουσία Excel συχνά όχι, επιβιώνοντας ως ορφανή που κρατά κλειδώματα αρχείων και δηλητηριάζει την επόμενη εκτέλεση. Όποιος έχει δει έντεκα περιπλανώμενες διεργασίες EXCEL.EXE να στοιβάζονται κάτω από έναν λογαριασμό υπηρεσίας γνωρίζει το υπόλοιπο αυτής της ιστορίας

Διάγραμμα που αντιπαραθέτει μια υπηρεσία Delphi που οδηγεί το EXCEL.EXE μέσω αυτοματισμού COM, όπου κρυφά διαλογικά παράθυρα και ορφανές διεργασίες μπλοκάρουν κλήσεις, με το HotXLS που γράφει bytes βιβλίου εργασίας BIFF8 και OOXML απευθείας μέσα στη διεργασία
Ο αυτοματισμός COM κληρονομεί τις ελλιπείς υποθέσεις ενός προγράμματος desktop, ενώ το HotXLS γράφει byte BIFF8 και OOXML απευθείας χωρίς τίποτα προς εγκατάσταση στον διακομιστή

Η ιστορία της κλιμάκωσης δεν είναι καλύτερη ακόμη κι όταν τίποτα δεν καταρρέει. Μια παρουσία Excel είναι μια διοχέτευση ενός βιβλίου εργασίας τη φορά, κάθε πρόσβαση ιδιότητας πληρώνει το κόστος του cross-process COM marshaling, και το μηχάνημα που τρέχει τον κώδικα κουβαλά μια άδεια Office της οποίας οι όροι αποκλείουν ακριβώς αυτή τη χρήση. Οι περισσότερες ομάδες συναντούν αυτά τα όρια ένα outage τη φορά, που είναι περίπου ο τρόπος με τον οποίο το «απόσυρσε το επίπεδο COM» καταλήγει σε ένα roadmap

Πριν ξεκινήσει εκείνη η επανεγγραφή, διευθετήστε ένα ζήτημα εμβέλειας, επειδή αποφασίζει πόσο πραγματική είναι η δουλειά. Ο κώδικας COM σχεδόν ποτέ δεν ορίζει απλώς τιμές κελιών. Καλεί την Workbook.SaveAs με σταθερές μορφής, εξαναγκάζει επανυπολογισμό, σπρώχνει ρύθμιση εκτύπωσης, μερικές φορές φτάνει στο πρόχειρο (clipboard). Διατρέξτε τον παλιό κώδικα και σημειώστε ποιες από αυτές τις συμπεριφορές πράγματι καταλήγουν στην έξοδο, αφού καθεμία προσγειώνεται σε διαφορετική γωνιά μιας εγγενούς βιβλιοθήκης, και μερικές από αυτές (η διαλειτουργικότητα πρόχειρου είναι η προφανής) δεν έχουν καμία σημασία στην πλευρά του διακομιστή και πρέπει να αποβληθούν αντί να μεταφερθούν

Δύο εγγενείς μηχανές, δύο μοντέλα κυριότητας

Το HotXLS αντικαθιστά τη διεργασία Excel με δύο άμεσες υλοποιήσεις μορφής. Μια μηχανή ροής εγγραφών BIFF8 (TXLSWorkbook, μονάδα lxHandle) χειρίζεται το .xls. Ένας συγγραφέας πακέτου OOXML (TXLSXWorkbook, μονάδα lxHandleX) παράγει .xlsx που συμμορφώνεται με το ECMA-376 / ISO/IEC 29500. Δεν υπάρχει τίποτα να καταχωρίσετε και τίποτα να εγκαταστήσετε στον διακομιστή, και μπορείτε να κρατάτε ανοιχτά όσα βιβλία εργασίας επιτρέπει η μνήμη ταυτόχρονα

Αυτό που μπερδεύει τον κόσμο νωρίς είναι ότι οι δύο προσόψεις κατέχουν τη μνήμη τους διαφορετικά, και η διαφορά είναι σιωπηλή μέχρι να καταρρεύσει:

var
  Book: IXLSWorkbook;          // αναφορά διεπαφής: ελευθερώνεται αυτόματα
  Sheet: IXLSWorksheet;
  BookX: TXLSXWorkbook;        // απλό αντικείμενο: το ελευθερώνετε εσείς
  SheetX: TXLSXWorksheet;
begin
  // έξοδος BIFF8 .xls - χωρίς Free· το refcount της διεπαφής το κατέχει
  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 - ρητή διάρκεια ζωής
  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 έχει μέτρηση αναφορών μέσω της διεπαφής IXLSWorkbook. Δηλώστε τη μεταβλητή ως τύπο διεπαφής και ποτέ μην καλείτε Free πάνω της· κρατήστε το ίδιο αντικείμενο σε μια απλή μεταβλητή αντικειμένου και ελευθερώστε το εσείς οι ίδιοι, και το refcount το ελευθερώνει μια δεύτερη φορά. Η πρόσοψη XLSX είναι ένα συνηθισμένο αντικείμενο που θέλει ένα συνηθισμένο try..finally. Η διευθυνσιοδότηση κελιών ξεκινά από το ένα και στις δύο πλευρές, που είναι το ένα σημείο όπου συμφωνούν. Οι συλλογές φύλλων όχι: η Entries στην πλευρά XLS ξεκινά από το ένα, ο δείκτης Items του XLSX ξεκινά από το μηδέν, και αυτό το off-by-one μεταγλωττίζεται καθαρά όποιον τρόπο κι αν το κάνετε λάθος και εμφανίζεται μόνο κατά τον χρόνο εκτέλεσης

Εγγραφή ενός βιβλίου εργασίας απευθείας σε μια απόκριση HTTP

Μια εξαγωγή από την πλευρά του διακομιστή συνήθως δεν έχει κανέναν λόγο να αγγίξει τον δίσκο. Τα προσωρινά αρχεία απαιτούν μια πολιτική εκκαθάρισης, συγκρούονται κάτω από ταυτόχρονα αιτήματα, και αφήνουν δεδομένα πελατών να κάθονται σε τόμους που κανείς δεν σκέφτηκε να ελέγξει. Και οι δύο προσόψεις δέχονται ένα TStream μέσω των υπερφορτώσεων SaveAs, οπότε το βιβλίο εργασίας μπορεί να πάει απευθείας στην απόκριση:

Διάγραμμα που συγκρίνει τις δύο προσόψεις HotXLS Delphi: το TXLSWorkbook απελευθερώνεται αυτόματα μέσω refcounting διεπαφής IXLSWorkbook, και το TXLSXWorkbook ως απλό αντικείμενο που χρειάζεται ρητό Free σε μπλοκ try..finally
Η πρόσοψη XLS απελευθερώνεται με μέτρηση αναφορών διεπαφής ενώ η πρόσοψη XLSX χρειάζεται ρητό Free, και οι συλλογές φύλλων διαφέρουν ανάμεσα σε Entries βάσης 1 και Items βάσης 0
Mem := TMemoryStream.Create;
Book := TXLSXWorkbook.Create;
try
  Sheet := Book.Sheets.Add('Data');
  Sheet.Cells[1, 1].Value := 'Generated ' + DateTimeToStr(Now);
  Book.SaveAs(Mem);          // γράφει από την ΤΡΕΧΟΥΣΑ θέση του stream
  Mem.Position := 0;         // περιτύλιξε πίσω πριν παραδώσετε το stream
  Response.ContentType :=
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
  Response.ContentStream := Mem;   // το framework κατέχει τώρα το Mem
finally
  Book.Free;
end;

Η περιτύλιξη πίσω είναι η γραμμή που δικαιολογεί το σχόλιό της. Η SaveAs(Stream) γράφει από την τρέχουσα θέση του stream και ποτέ δεν γυρίζει πίσω στο μηδέν αργότερα. Ξεχάστε την Mem.Position := 0 και ο πελάτης παίρνει μια λήψη μηδέν bytes, ή το Excel δηλώνει το αρχείο διεφθαρμένο. Αυτό είναι το πιο συχνό σφάλμα σε κώδικα βιβλίου εργασίας προσανατολισμένο στο web, και το πιο σκληρό, επειδή περνά ατιμώρητο από κάθε unit test που απλώς επιβεβαιώνει ότι το stream έχει μη μηδενικό μήκος

Μία ρουτίνα κατασκευής βιβλίου εργασίας φτάνει σε κάθε άλλη μορφή παράδοσης χωρίς αναδιάρθρωση. Η SaveAsCSV απαντά στο αίτημα «απλώς δώσε μου τα ακατέργαστα δεδομένα», η SaveAsHTML χειρίζεται το «βάλε το σε μια σελίδα πύλης», η SaveAsRTF τροφοδοτεί διοχετεύσεις εγγράφων, και η SaveAsODS καλύπτει μια επιταγή OpenDocument, όλα με υπερφορτώσεις και αρχείου και stream. Μία ενιαία ρουτίνα εξαγωγής συν μια παράμετρος μορφής αντικαθιστά αυτό που τείνει να ήταν τέσσερις ξεχωριστές μακροεντολές COM. Το TXLSXHtmlExportOptions του εξαγωγέα HTML κουβαλά τίτλο, κλάση CSS, και έναν διακόπτη απόσπασμα-ή-πλήρες-έγγραφο, κάτι που κρατά την περίπτωση πύλης έξω από τη δουλειά της επεξεργασίας εξαγόμενης σήμανσης με regex

Διάγραμμα χειριστή αιτημάτων Delphi που αποθηκεύει βιβλίο εργασίας HotXLS σε TMemoryStream, επαναφέρει το Mem.Position στο μηδέν, και παραδίδει το stream στην απάντηση HTTP, με τους εξαγωγείς CSV, HTML, RTF και ODS δίπλα
Η αποθήκευση σε TMemoryStream και η επαναφορά του στην αρχή πριν την παράδοση στέλνει byte βιβλίου εργασίας κατευθείαν στον πελάτη, και μία ρουτίνα εξαγωγής καλύπτει τους writers CSV, HTML, RTF και ODS

Τιμές τύπων χωρίς μια διεργασία Excel να τις υπολογίσει

Κάτω από COM automation το Excel επανυπολόγιζε τα πάντα δωρεάν, και η εγκατάλειψη του COM ανακαλεί σιωπηλά αυτό. Η SaveAs αποθηκεύει τύπους ως κείμενο χωρίς να τους αποτιμά· οι αριθμοί εμφανίζονται μόνο όταν το Excel ανοίγει το αρχείο και επανυπολογίζει, συμπεριφορά που η πρόσοψη XLS σάς αφήνει να συντονίσετε μέσω των RecalcOnSave και CalculationMode. Για ένα αρχείο που προορίζεται για έναν άνθρωπο αυτό είναι ακριβώς σωστό. Είναι λάθος για μια υπηρεσία που πρέπει να επιβεβαιώσει ένα σύνολο πριν το αποστείλει, και λάθος για εξαγωγή CSV, που γράφει το κείμενο του τύπου αντί για το αποτέλεσμά του. Και οι δύο περιπτώσεις πρέπει να αποτιμηθούν στον διακομιστή με την ενσωματωμένη μηχανή:

SheetX.Cells[1, 1].Value := 1200;
SheetX.Cells[2, 1].Value := 950;
SheetX.Cells[3, 1].Formula := 'SUM(A1:A2)';   // πρόσοψη XLSX: χωρίς πρόθεμα '='
Total := BookX.Calculate('SUM(A1:A2)');       // αποτίμησε στον διακομιστή, τώρα
if Total <> 2150 then
  raise Exception.Create('reconciliation failed before delivery');

Η σύμβαση προσόψεων δαγκώνει ξανά εδώ. Η πλευρά XLSX αναθέτει εκφράσεις μέσω της Cell.Formula χωρίς σύμβολο ίσον· η πλευρά XLS τις γράφει μέσω της Cell.Value με αρχικό '='. Μεταφέρετε κώδικα από τη μία στην άλλη αναλλοίωτο και η λάθος σύμβαση αποθηκεύει μια συμβολοσειρά κειμένου που απλώς μοιάζει με τύπο, χωρίς κανένα σφάλμα να το σημάνει. Όταν οι τύποι ενός βιβλίου εργασίας χρειάζεται να φτάσουν μέσα στη δική σας επιχειρηματική λογική, το callback OnUserFunction επιτρέπει στη μηχανή να παραδώσει άγνωστα ονόματα συναρτήσεων σε κώδικα Delphi κατά τη στιγμή της αποτίμησης. Αυτό είναι το εγγενές υποκατάστατο για τα πρόσθετα UDF που τείνουν να κρύβονται μέσα στα ίδια τα υπολογιστικά φύλλα γύρω από τα οποία μεγάλωσε ένα σύστημα COM-automation

Άκρες ανάπτυξης που αναδύονται μόνο στον διακομιστή

Μερικές λεπτομέρειες αποφασίζουν αν η ανάπτυξη είναι καθαρή ή μπερδεμένη, και η πρώτη είναι ο γράφος μονάδων. Ο εξαγωγέας dataset drag-and-drop TDataToXLS τραβά μέσα τις VCL Forms, Controls, και Dialogs. Αβλαβές σε ένα εργαλείο desktop· σε μια υπηρεσία κονσόλας σέρνει ολόκληρο το VCL μαζί του. Οι βασικές μονάδες lxHandle και lxHandleX φτάνουν μόνο στις Windows, Classes, SysUtils, και Variants, οπότε μια καθαρή υπηρεσία είναι καλύτερα να γράφει τον δικό της βρόχο dataset πάνω στο βασικό API παρά να εισάγει το εξάρτημα για ευκολία

Έπειτα υπάρχει το threading. Οι παρουσίες βιβλίου εργασίας δεν είναι thread-safe, αλλά δεν μοιράζονται και καμία καθολική κατάσταση, οπότε το μοτίβο που κλιμακώνεται είναι το απλούστερο: ένα αντικείμενο βιβλίου εργασίας ανά εργασία, ή ανά worker thread. Αυτό αγοράζει παράλληλη δημιουργία αναφορών, κάτι που μια μοναδική κοινόχρηστη παρουσία Excel δεν μπορεί ποτέ να κάνει. Ένας χειριστής αιτημάτων που δημιουργεί, γεμίζει, αποθηκεύει, και ελευθερώνει το δικό του βιβλίο εργασίας δεν χρειάζεται καθόλου locks, και η ακτίνα καταστροφής μιας αστοχίας καταρρέει από «η κοινόχρηστη παρουσία Excel είναι σφηνωμένη για όλους» σε «αυτό το ένα αίτημα σήκωσε μια εξαίρεση», κάτι που ο υπάρχων χειρισμός σφαλμάτων σας ήδη ξέρει τι να κάνει

Ο στοχευμένος τύπος μορφής είναι το τελευταίο τους. Η TXLSWorkbook.SaveAs γράφει BIFF (xlExcel97) από προεπιλογή, και το σπρώξιμο περιεχομένου XLS μέσα σε .xlsx περνά μέσα από τη γέφυρα SaveXLSWorkbookAsXLSX με μειωμένη πιστότητα. Επιλέξτε την πρόσοψη ανάλογα με τη μορφή που σκοπεύετε να αποστείλετε, κατά τον χρόνο σχεδιασμού, αντί να χτίζετε στη μία και να μετατρέπετε στο τέλος της διοχέτευσης

Για το μισό φόρτωσης δεδομένων ενός τυπικού έργου αντικατάστασης, τα μοτίβα εξαγωγής από βάση δεδομένων σε βιβλίο εργασίας καλύπτουν και το εξάρτημα και τον χειρόγραφο βρόχο, και μόλις ο αριθμός γραμμών φτάσει τα εξαψήφια, οι τεχνικές απόδοσης μεγάλων βιβλίων εργασίας γίνονται η διαφορά ανάμεσα σε λεπτά και δευτερόλεπτα. Αναφορές χτισμένες από διατάξεις που συντηρεί σχεδιαστής καλύπτονται στον οδηγό δημιουργίας αναφορών βάσει template

Το HotXLS διατίθεται ως πηγαίος κώδικας Object Pascal για Delphi και C++Builder· οι εκδόσεις, η αδειοδότηση, και η πλήρης αναφορά API βρίσκονται στη σελίδα προϊόντος HotXLS Delphi Component