Een hardgecodeerde werkbladverwijzing hernoemen over duizend macro-ingeschakelde rapportsjablonen sluit uit dat u elk bestand handmatig opent in de VBA-editor. HotXLS, de native Delphi- en C++Builder-Excel-component, handelt dit geval af door de broncode van een VBA-module bloot te stellen als een bewerkbare SourceCode-eigenschap en elke bewerking opnieuw te comprimeren met het MS-OVBA-compressie-algoritme dat Microsoft definieert voor VBA-opslag, waarbij het resultaat wordt teruggeschreven naar klassieke XLS VBA-opslag, een op zichzelf staand VBA-projectbestand, of een macro-ingeschakelde XLSM-werkmap. Er komt nergens in dat pad een Excel-instantie, een VBA-editor, of een macro-recorder aan te pas
Waarom een VBA-modulestream geen tekstbestand is
Een VBA-module binnen een XLS-werkmap of een op zichzelf staand VBA-projectbestand is geen brontekst die in een stream ligt te wachten om gelezen te worden — het is een kleine binaire container. Eerst komt een gecompileerde prestatiecache, de bytes die Office gebruikt om het opnieuw compileren van de module bij het laden over te slaan wanneer de cache nog overeenkomt met de hostversie, en daarna volgt de eigenlijke brontekst, verwerkt door een eigen compressieschema dat MS-OVBA specifiek definieert voor VBA-opslag. Dat schema is geen zip, geen deflate, en niets wat de Windows-compressie-API's van nature produceren, en precies daarom kunnen de meeste externe Excel-bibliotheken wel de broncode van een module lezen — decompressie is de eenvoudigere helft van het probleem — maar schieten ze tekort zodra het gaat om terugschrijven, aangezien recompressie het punt is waarop een subtiel verkeerd bit een bestand oplevert dat Excel weigert te openen. Publieke beschrijvingen van de leeskant bestaan; schrijfkant-implementaties die daadwerkelijk recompressie uitvoeren, in plaats van alleen een bestaande module uit te pakken voor inspectie, zijn schaars genoeg om dit een van de minst gedocumenteerde hoeken van de Excel-bestandsformaten te maken
Wat verandert de SourceCode-eigenschap van HotXLS precies?
HotXLS representeert elke VBA-module als een TXLSVBAModule-object met een eenvoudige eigenschap SourceCode: WideString, en er een nieuwe waarde aan toewijzen is precies zo eenvoudig als het lijkt: de module wordt in het geheugen als gewijzigd gemarkeerd, en niets raakt de onderliggende OLE-stream totdat het project wordt opgeslagen. Het project zelf komt van IXLSWorkbook.VBAProject op de klassieke XLS-engine of TXLSXWorkbook.ParsedVBAProject op de OOXML macro-ingeschakelde engine, beide geven een TXLSVBAProject terug waarvan de modules achter een 1-gebaseerde Item[]-indexer en een Count-eigenschap zitten, dus een batchgewijze bewerking over elke module in een werkmap is gewoon een lus over een geheel getalbereik
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;
Die lus is ook de vorm van een auditronde. Voordat duizend sjablonen worden aangeraakt, willen de meeste teams eerst weten hoeveel ervan daadwerkelijk macro's bevatten en waar die macro's naar verwijzen, wat het scenario is achter de audit- en conversieworkbench voor werkmappen — dezelfde Project.Count die hier een herschrijflus aandrijft, wordt daar een macrotelling per bestand
Binnen in de MS-OVBA-compressiecontainer
Het compressieformaat van MS-OVBA verpakt bronbytes in wat de specificatie een CompressedContainer noemt: een enkele signatuurbyte, verplicht gelijk aan 0x01, gevolgd door een reeks CompressedChunk-blokken, elk bestrijkend tot 4096 bytes gedecomprimeerde data. Een 16-bits chunkheader draagt drie velden — een 3-bits signatuur die gelijk moet zijn aan 3, een 12-bits groottevel, en een CompressedChunkFlag-bit die aangeeft of de payload van de chunk letterlijke bytes zijn of een token-gecomprimeerde reeks. Wanneer de vlag is ingesteld, is de payload een reeks vlagbyte-voorafgegane groepen van acht tokens, en elk token is ofwel een enkele letterlijke byte ofwel een CopyToken: een offset/lengte-terugverwijzing naar bytes die eerder in dezelfde chunk al zijn gedecomprimeerd, waarbij de bitbreedte-verdeling tussen offset en lengte verschuift afhankelijk van hoever de decompressor zich al in de chunk bevindt. Dit deel van MS-OVBA (§2.4.1, Compression and Decompression) is waar een handgeschreven implementatie het vaakst een dag verliest aan een off-by-one in die bitbreedteberekening
Waarom HotXLS ruwe chunks schrijft in plaats van tokens te matchen
Het schrijfpad van HotXLS omzeilt de token-matchende helft van dat algoritme volledig. Wanneer het een bewerkte module opnieuw comprimeert, gaat elke chunk eruit met de CompressedChunkFlag gewist, wat betekent dat de chunk letterlijke bytes bevat in plaats van terugverwijzingstokens — legaal onder MS-OVBA, aangezien een gecomprimeerde container volledig uit ongecomprimeerde chunks mag bestaan, en het verwijdert precies het deel van het algoritme dat het moeilijkst met de hand goed te krijgen is: geldige terugverwijzingen vinden en een offset/lengte-paar inpakken in een bitbreedte die afhankelijk is van de huidige positie binnen de chunk. De afweging komt tot uiting in de bestandsgrootte, niet in de correctheid — een herschreven modulestream komt uit dicht bij de grootte van de brontekst plus een header van twee bytes per blok van 4096 bytes, niet kleiner zoals een volledig token-gecomprimeerde chunk zou zijn. Elke lezer die de decompressiekant van de specificatie implementeert, Excel inbegrepen, opent het resultaat nog steeds correct, omdat een ruwe chunk net zo geldig een CompressedChunk is als een token-gecomprimeerde
Wat HotXLS ongemoeid laat bij het herschrijven van een module
Recompressie vervangt altijd maar een deel van de modulestream. Elke modulestream slaat eerst zijn prestatiecache op en vervolgens zijn gecomprimeerde broncode, en de dir-stream van het project registreert precies waar die scheiding voor elke module valt in een MODULEOFFSET-item; HotXLS leest die offset, behoudt elke byte ervoor precies zoals aangetroffen, en herbouwt alleen de gecomprimeerde container vanaf de offset
De brontekst zelf gaat heen en weer via de eigen codepagina van het VBA-project in plaats van UTF-8 — dezelfde legacy-codepagina waarmee Office het project oorspronkelijk schreef. Een SourceCode-bewerking die tekens introduceert buiten het repertoire van die codepagina, wordt stilzwijgend vervangen door best-fit-vervangingstekens wanneer HotXLS de string weer terugcodeert naar bytes, niet afgewezen, dus een ongebruikelijk regionaal teken dat in een commentaar of stringliteraal terechtkomt, is de meest waarschijnlijke plek om het verlies op te merken. Externe verwijzingen en bibliotheekbindingen binnen hetzelfde project volgen een verwant maar apart behoudspad, behandeld in het begeleidende artikel over het behoud van externe VBA-links, en het is de moeite waard om dat te lezen voordat een herschrijfronde een project raakt dat naar andere werkmappen of typebibliotheken verwijst
Hoe krijgt u de herschreven macro's terug in een werkmap?
Niets roept de recompressiestap expliciet aan — deze draait automatisch op het moment dat een werkmap of een op zichzelf staand VBA-project wordt opgeslagen. TXLSVBAProject.ApplyChanges loopt door elke module, comprimeert opnieuw diegene waarvan SourceCode sinds de laatste opslag is gewijzigd, en herschrijft alleen de stream van die module; zowel het klassieke TXLSWorkbook.SaveAs, wanneer het opslagdoel het oorspronkelijke bestandsformaat behoudt, als het OOXML TXLSXWorkbook.SaveAs voor een macro-ingeschakeld XLSM-pakket, roepen dit intern aan voordat er iets naar schijf wordt geschreven, en SaveVBAProjectToFile roept dezelfde methode aan wanneer het doel een losstaand VBA-projectbestand is in plaats van een volledige werkmap
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;
Alle drie de bestemmingen delen onderliggend dezelfde SourceCode- en ApplyChanges-mechaniek; het enige echte verschil ertussen is welke opslagaanroep uiteindelijk de recompressie in gang zet
Waar dit nog steeds misgaat
Twee faalmodi komen vaak genoeg voor om rekening mee te houden voordat een herschrijfronde tegen productiebestanden wordt uitgevoerd. Een digitaal ondertekend VBA-project stopt geldig ondertekend te zijn zodra de broncode ervan verandert, aangezien de handtekening de inhoud van het project dekt; HotXLS heeft geen manier om een project namens u opnieuw te ondertekenen, en Excel laat de handtekening vallen of markeert deze de volgende keer dat het bestand wordt geopend, dus een ondertekend macroproject heeft stroomafwaarts een stap voor het opnieuw ondertekenen nodig als die handtekening iets is wat uw workflow daadwerkelijk controleert. De tweede faalmodus treft iedereen die verleid wordt om dit compressieformaat vanaf nul zelf te implementeren in plaats van een bibliotheek te gebruiken die het al afhandelt: één verkeerd bit in een chunkheader, in de signatuur-nibble, het groottevel, of de compressievlag, levert een bestand op dat Excel weigert te openen, meestal achter een generieke corruptiewaarschuwing die geen aanwijzing geeft welke byte fout was — precies de klasse bug die de eerder beschreven ruwe-chunk-schrijfstrategie probeert te vermijden
Niets hiervan vereist reverse-engineering van het formaat om te gebruiken. Delphi- en C++Builder-ontwikkelaars krijgen lees- en schrijftoegang tot SourceCode, MS-OVBA-conforme recompressie, en alle drie hier beschreven terugschrijfbestemmingen als onderdeel van de standaard HotXLS-component, naast de rest van de klassieke XLS- en OOXML-werkmap-API