Η μετονομασία μιας σκληρά κωδικοποιημένης αναφοράς φύλλου εργασίας σε χίλια πρότυπα αναφορών με μακροεντολές αποκλείει το άνοιγμα κάθε αρχείου στον επεξεργαστή VBA με το χέρι. Το HotXLS, το γηγενές εξάρτημα Excel για Delphi και C++Builder, χειρίζεται αυτή την περίπτωση εκθέτοντας τον πηγαίο κώδικα μιας μονάδας VBA ως επεξεργάσιμη ιδιότητα SourceCode και επανασυμπιέζοντας κάθε επεξεργασία με τον αλγόριθμο συμπίεσης MS-OVBA που ορίζει η Microsoft για την αποθήκευση VBA, γράφοντας το αποτέλεσμα πίσω σε αποθήκευση VBA κλασικού XLS, ένα αυτοτελές αρχείο έργου VBA, ή ένα βιβλίο εργασίας XLSM με μακροεντολές. Καμία περίπτωση Excel, κανένας επεξεργαστής VBA, και κανένας καταγραφέας μακροεντολών δεν εμπλέκεται πουθενά σε εκείνη τη διαδρομή
Γιατί μια ροή μονάδας VBA δεν είναι αρχείο κειμένου
Μια μονάδα VBA μέσα σε ένα βιβλίο εργασίας XLS ή ένα αυτοτελές αρχείο έργου VBA δεν είναι πηγαίο κείμενο που κάθεται σε μια ροή περιμένοντας να διαβαστεί — είναι ένα μικρό δυαδικό δοχείο. Πρώτα έρχεται μια μεταγλωττισμένη προσωρινή μνήμη απόδοσης, τα byte που χρησιμοποιεί το Office για να παραλείψει την επαναμεταγλώττιση της μονάδας κατά τη φόρτωση όταν η προσωρινή μνήμη εξακολουθεί να ταιριάζει με την έκδοση host, και το πραγματικό πηγαίο κείμενο ακολουθεί, περνώντας μέσα από ένα ιδιόκτητο σχήμα συμπίεσης που ορίζει ειδικά το MS-OVBA για την αποθήκευση VBA. Εκείνο το σχήμα δεν είναι zip, δεν είναι deflate, και δεν είναι τίποτα που παράγουν εγγενώς τα API συμπίεσης των Windows, κάτι που εξηγεί ακριβώς γιατί οι περισσότερες βιβλιοθήκες Excel τρίτων μερών μπορούν να διαβάσουν τον πηγαίο κώδικα μιας μονάδας — η αποσυμπίεση είναι το ευκολότερο μισό του προβλήματος — ενώ σταματούν πριν να τον ξαναγράψουν πίσω, αφού η επανασυμπίεση είναι εκεί όπου ένα λεπτά λάθος bit παράγει ένα αρχείο που το Excel αρνείται να ανοίξει. Δημόσια κείμενα για την πλευρά ανάγνωσης υπάρχουν· υλοποιήσεις της πλευράς εγγραφής που πράγματι ασκούν επανασυμπίεση, αντί απλώς να ξεπακετάρουν μια υπάρχουσα μονάδα για επιθεώρηση, είναι αρκετά σπάνιες ώστε αυτό να παραμένει μία από τις λιγότερο τεκμηριωμένες γωνίες των μορφών αρχείων Excel
Τι πράγματι αλλάζει η ιδιότητα SourceCode του HotXLS;
Το HotXLS αναπαριστά κάθε μονάδα VBA ως αντικείμενο TXLSVBAModule με μια απλή ιδιότητα SourceCode: WideString, και η ανάθεση σε αυτήν μιας νέας τιμής είναι ακριβώς τόσο απλή όσο φαίνεται: η μονάδα σημειώνεται ως "βρώμικη" στη μνήμη, και τίποτα δεν αγγίζει την υποκείμενη ροή OLE μέχρι να αποθηκευτεί το έργο. Το ίδιο το έργο προέρχεται από IXLSWorkbook.VBAProject στη μηχανή κλασικού XLS ή TXLSXWorkbook.ParsedVBAProject στη μηχανή OOXML με μακροεντολές, αμφότερα επιστρέφοντας ένα TXLSVBAProject του οποίου οι μονάδες κάθονται πίσω από έναν δείκτη Item[] βασισμένο στο 1 και μια ιδιότητα Count, οπότε μια μαζική επεξεργασία σε κάθε μονάδα ενός βιβλίου εργασίας είναι απλώς ένας βρόχος πάνω σε ένα εύρος ακεραίων
var
Wb: TXLSWorkbook;
Project: TXLSVBAProject;
I: Integer;
Updated: WideString;
begin
Wb := TXLSWorkbook.Create;
try
Wb.Open('MonthlyReport.xls');
if Wb.HasVBAProject then
begin
Project := Wb.VBAProject;
for I := 1 to Project.Count do
begin
Updated := StringReplace(Project[I].SourceCode,
'ReportSheet2025', 'ReportSheet2026', [rfReplaceAll]);
if Updated <> Project[I].SourceCode then
Project[I].SourceCode := Updated; // marks the module dirty
end;
Wb.SaveAs('MonthlyReport.xls'); // recompresses on write
end;
finally
Wb.Free;
end;
end;
Εκείνος ο βρόχος είναι επίσης το σχήμα ενός περάσματος ελέγχου. Πριν αγγιχτούν χίλια πρότυπα, οι περισσότερες ομάδες πρώτα θέλουν να γνωρίζουν πόσα από αυτά πράγματι φέρουν μακροεντολές και σε τι αναφέρονται εκείνες οι μακροεντολές, κάτι που είναι το σενάριο πίσω από τον πάγκο εργασίας ελέγχου και μετατροπής βιβλίου εργασίας — το ίδιο Project.Count που οδηγεί έναν βρόχο επανεγγραφής εδώ γίνεται εκεί μια μέτρηση μακροεντολών ανά αρχείο
Μέσα στο δοχείο συμπίεσης MS-OVBA
Η μορφή συμπίεσης του MS-OVBA πακετάρει byte πηγαίου κώδικα σε αυτό που η προδιαγραφή αποκαλεί CompressedContainer: ένα μοναδικό byte υπογραφής, που πρέπει να ισούται με 0x01, ακολουθούμενο από μια ακολουθία μπλοκ CompressedChunk, καθένα καλύπτοντας έως 4096 byte αποσυμπιεσμένων δεδομένων. Μια κεφαλίδα τμήματος 16-bit φέρει τρία πεδία — μια υπογραφή 3-bit που πρέπει να ισούται με 3, ένα πεδίο μεγέθους 12-bit, και ένα bit CompressedChunkFlag που σημειώνει αν το φορτίο του τμήματος είναι literal byte ή μια ακολουθία συμπιεσμένη με tokens. Όταν η σημαία είναι ορισμένη, το φορτίο είναι μια σειρά ομάδων οκτώ tokens με πρόθεμα byte σημαίας, και κάθε token είναι είτε ένα μοναδικό literal byte είτε ένα CopyToken: μια αναφορά προς τα πίσω μετατόπισης/μήκους σε byte που έχουν ήδη αποσυμπιεστεί νωρίτερα στο ίδιο τμήμα, με το πλάτος bit διαιρεμένο μεταξύ μετατόπισης και μήκους να αλλάζει ανάλογα με το πόσο βαθιά μέσα στο τμήμα κάθεται αυτή τη στιγμή ο αποσυμπιεστής. Αυτό το μέρος του MS-OVBA (§2.4.1, Συμπίεση και Αποσυμπίεση) είναι εκεί όπου μια χειρόγραφη υλοποίηση πιο συχνά χάνει μια μέρα σε ένα σφάλμα κατά ένα σε εκείνον τον υπολογισμό πλάτους bit
Γιατί το HotXLS γράφει ακατέργαστα τμήματα αντί να ταιριάζει tokens
Η διαδρομή εγγραφής του HotXLS παρακάμπτει εντελώς το μισό ταιριάσματος tokens εκείνου του αλγορίθμου. Όταν επανασυμπιέζει μια επεξεργασμένη μονάδα, κάθε τμήμα βγαίνει με το CompressedChunkFlag καθαρισμένο, που σημαίνει ότι το τμήμα κρατά literal byte αντί για tokens αναφοράς προς τα πίσω — νόμιμο κάτω από το MS-OVBA, αφού ένα συμπιεσμένο δοχείο επιτρέπεται να αποτελείται εξ ολοκλήρου από ασυμπίεστα τμήματα, και αφαιρεί ακριβώς το μέρος του αλγορίθμου που είναι δυσκολότερο να γίνει σωστά με το χέρι: η εύρεση έγκυρων αναφορών προς τα πίσω και η συσκευασία ενός ζεύγους μετατόπισης/μήκους σε ένα πλάτος bit που εξαρτάται από την τρέχουσα θέση μέσα στο τμήμα. Ο συμβιβασμός εμφανίζεται στο μέγεθος αρχείου, όχι στην ορθότητα — μια ξαναγραμμένη ροή μονάδας καταλήγει κοντά στο μέγεθος του πηγαίου κειμένου της συν μια κεφαλίδα δύο byte ανά μπλοκ 4096 byte, όχι μικρότερη με τον τρόπο που θα ήταν ένα πλήρως συμπιεσμένο με tokens τμήμα. Κάθε αναγνώστης που υλοποιεί την πλευρά αποσυμπίεσης της προδιαγραφής, το Excel συμπεριλαμβανομένου, εξακολουθεί να ανοίγει σωστά το αποτέλεσμα, επειδή ένα ακατέργαστο τμήμα είναι εξίσου έγκυρο CompressedChunk όσο και ένα συμπιεσμένο με tokens
Τι αφήνει το HotXLS ανέγγιχτο όταν ξαναγράφει μια μονάδα
Η επανασυμπίεση αντικαθιστά πάντα μόνο μέρος της ροής μονάδας. Κάθε ροή μονάδας αποθηκεύει πρώτα την προσωρινή μνήμη απόδοσής της και δεύτερο το συμπιεσμένο πηγαίο κώδικά της, και η ροή dir του έργου καταγράφει ακριβώς πού πέφτει εκείνος ο διαχωρισμός για κάθε μονάδα σε μια καταχώριση MODULEOFFSET· το HotXLS διαβάζει εκείνη τη μετατόπιση, κρατά κάθε byte πριν από αυτήν ακριβώς όπως τα βρήκε, και ανακατασκευάζει μόνο το συμπιεσμένο δοχείο από τη μετατόπιση και μετά
Το ίδιο το πηγαίο κείμενο κάνει πήγαινε-έλα μέσω της δικής της σελίδας κώδικα του έργου VBA αντί για UTF-8 — την ίδια παλαιά σελίδα κώδικα με την οποία το Office έγραψε το έργο εξαρχής. Μια επεξεργασία SourceCode που εισάγει χαρακτήρες έξω από το ρεπερτόριο εκείνης της σελίδας κώδικα αντικαθίσταται σιωπηλά με χαρακτήρες αντικατάστασης βέλτιστης προσαρμογής όταν το HotXLS ξανακωδικοποιεί τη συμβολοσειρά πίσω σε byte, όχι απορρίπτεται, οπότε ένας ασυνήθιστος περιφερειακός χαρακτήρας ριγμένος σε ένα σχόλιο ή literal συμβολοσειράς είναι το πιο πιθανό σημείο για να παρατηρήσετε την απώλεια. Εξωτερικές αναφορές και δεσμεύσεις βιβλιοθηκών μέσα στο ίδιο έργο ακολουθούν μια σχετική αλλά ξεχωριστή διαδρομή διατήρησης, που καλύπτεται στο συνοδευτικό άρθρο για τη διατήρηση εξωτερικών συνδέσμων VBA, και αξίζει να το διαβάσετε πριν ένα πέρασμα επανεγγραφής αγγίξει ένα έργο που συνδέεται με άλλα βιβλία εργασίας ή βιβλιοθήκες τύπων
Πώς παίρνετε πίσω τις ξαναγραμμένες μακροεντολές σε ένα βιβλίο εργασίας;
Τίποτα δεν καλεί ρητά το βήμα επανασυμπίεσης — τρέχει αυτόματα τη στιγμή που αποθηκεύεται ένα βιβλίο εργασίας ή ένα αυτοτελές έργο VBA. Το TXLSVBAProject.ApplyChanges διατρέχει κάθε μονάδα, επανασυμπιέζει εκείνες των οποίων το SourceCode άλλαξε από την τελευταία αποθήκευση, και ξαναγράφει μόνο τη ροή εκείνης της μονάδας· το κλασικό TXLSWorkbook.SaveAs, όταν ο στόχος αποθήκευσης κρατά την αρχική μορφή του αρχείου, και το OOXML TXLSXWorkbook.SaveAs για ένα πακέτο XLSM με μακροεντολές αμφότερα το καλούν εσωτερικά προτού γραφτεί οτιδήποτε στον δίσκο, και το SaveVBAProjectToFile καλεί την ίδια μέθοδο όταν ο στόχος είναι ένα αποσπασμένο αρχείο έργου VBA παρά ένα πλήρες βιβλίο εργασίας
var
Wb: TXLSWorkbook;
begin
Wb := TXLSWorkbook.Create;
try
if Wb.LoadVBAProjectFromFile('LegacyMacros.ole') = 1 then
begin
Wb.VBAProject[1].SourceCode :=
StringReplace(Wb.VBAProject[1].SourceCode, 'OldServer', 'NewServer', [rfReplaceAll]);
Wb.SaveVBAProjectToFile('LegacyMacros_Patched.ole'); // ApplyChanges runs internally
end;
finally
Wb.Free;
end;
end;
var
Xlsx: TXLSXWorkbook;
Project: TXLSVBAProject;
begin
Xlsx := TXLSXWorkbook.Create;
try
Xlsx.Open('Dashboard.xlsm');
Project := Xlsx.ParsedVBAProject;
if Assigned(Project) then
begin
Project[1].SourceCode := StringReplace(Project[1].SourceCode,
'ConnStringV1', 'ConnStringV2', [rfReplaceAll]);
Xlsx.SaveAs('Dashboard.xlsm'); // SyncParsedVBAProject recompresses before the part is written
end;
finally
Xlsx.Free;
end;
end;
Και οι τρεις προορισμοί μοιράζονται τον ίδιο μηχανισμό SourceCode και ApplyChanges από κάτω· η μόνη πραγματική διαφορά μεταξύ τους είναι ποια κλήση αποθήκευσης καταλήγει να πυροδοτεί την επανασυμπίεση
Πού εξακολουθεί να σπάει αυτό
Δύο λειτουργίες αποτυχίας είναι αρκετά συνηθισμένες ώστε να αξίζει να τις σχεδιάσετε πριν ένα πέρασμα επανεγγραφής τρέξει έναντι αρχείων παραγωγής. Ένα ψηφιακά υπογεγραμμένο έργο VBA παύει να είναι έγκυρα υπογεγραμμένο τη στιγμή που αλλάζει ο πηγαίος κώδικάς του, αφού η υπογραφή καλύπτει το περιεχόμενο του έργου· το HotXLS δεν έχει τρόπο να ξαναυπογράψει ένα έργο εκ μέρους σας, και το Excel αφαιρεί ή σημαίνει την υπογραφή την επόμενη φορά που ανοίγει το αρχείο, οπότε ένα υπογεγραμμένο έργο μακροεντολών χρειάζεται ένα βήμα επανυπογραφής μεταγενέστερα αν εκείνη η υπογραφή είναι κάτι που πράγματι ελέγχει η ροή εργασίας σας. Η δεύτερη λειτουργία αποτυχίας ανήκει σε όποιον πειραστεί να επαναϋλοποιήσει αυτή τη μορφή συμπίεσης από την αρχή αντί να χρησιμοποιήσει μια βιβλιοθήκη που ήδη το χειρίζεται: ένα μοναδικό λάθος bit σε μια κεφαλίδα τμήματος, στο nibble υπογραφής, στο πεδίο μεγέθους, ή στη σημαία συμπίεσης, παράγει ένα αρχείο που το Excel αρνείται να ανοίξει, συνήθως πίσω από μια γενική προειδοποίηση αλλοίωσης που δεν δίνει καμία ένδειξη ποιο byte ήταν λάθος — ακριβώς η κατηγορία σφάλματος που η στρατηγική εγγραφής ακατέργαστου τμήματος που περιγράφηκε νωρίτερα υπάρχει για να αποφύγει
Τίποτα από αυτά δεν απαιτεί αντίστροφη μηχανική της μορφής για να το χρησιμοποιήσετε. Οι προγραμματιστές Delphi και C++Builder παίρνουν πρόσβαση ανάγνωσης και εγγραφής στο SourceCode, επανασυμπίεση συμμορφούμενη με το MS-OVBA, και και τους τρεις προορισμούς επανεγγραφής που περιγράφονται εδώ ως μέρος του τυπικού εξαρτήματος HotXLS, μαζί με το υπόλοιπο του API βιβλίου εργασίας κλασικού XLS και OOXML