Att döpa om en hårdkodad kalkylbladsreferens genomgående i tusen makroaktiverade rapportmallar utesluter att öppna varje fil i VBA-redigeraren för hand. HotXLS, den nativa Excel-komponenten för Delphi och C++Builder, hanterar det fallet genom att exponera en VBA-moduls källkod som en redigerbar SourceCode-egenskap och återkomprimera varje ändring med MS-OVBA-komprimeringsalgoritmen som Microsoft definierar för VBA-lagring, och skriver resultatet tillbaka till klassisk XLS VBA-lagring, en fristående VBA-projektfil, eller en makroaktiverad XLSM-arbetsbok. Ingen Excel-instans, ingen VBA-redigerare, och ingen makroinspelare är inblandad någonstans i den vägen
Varför en VBA-modulström inte är en textfil
En VBA-modul inuti en XLS-arbetsbok eller en fristående VBA-projektfil är inte källtext som ligger i en ström och väntar på att läsas — det är en liten binär behållare. Ett kompilerat prestandacache kommer först, byten som Office använder för att slippa kompilera om modulen vid inläsning när cachet fortfarande matchar värdversionen, och den faktiska källtexten följer, körd genom ett proprietärt komprimeringsschema som MS-OVBA definierar specifikt för VBA-lagring. Det schemat är inte zip, inte deflate, och inte något Windows komprimerings-API:er producerar naturligt, vilket är exakt varför de flesta tredjepartsbibliotek för Excel kan läsa en moduls källkod — dekomprimering är den enklare halvan av problemet — men stannar innan de kan skriva tillbaka den, eftersom återkomprimering är där en subtilt fel bit producerar en fil Excel vägrar öppna. Publika genomgångar av läsdelen finns; skrivdelsimplementationer som faktiskt utövar återkomprimering, snarare än att bara packa upp en befintlig modul för inspektion, är tillräckligt sällsynta att detta förblir ett av de minst dokumenterade hörnen av Excel-filformaten
Vad ändrar HotXLS SourceCode-egenskap egentligen?
HotXLS representerar varje VBA-modul som ett TXLSVBAModule-objekt med en enkel SourceCode: WideString-egenskap, och att tilldela den ett nytt värde är exakt så enkelt som det ser ut: modulen markeras smutsig i minnet, och inget rör den underliggande OLE-strömmen förrän projektet sparas. Projektet självt kommer från IXLSWorkbook.VBAProject på den klassiska XLS-motorn eller TXLSXWorkbook.ParsedVBAProject på den OOXML-baserade makroaktiverade motorn, båda returnerar ett TXLSVBAProject vars moduler ligger bakom en 1-baserad Item[]-indexerare och en Count-egenskap, så en batchredigering över varje modul i en arbetsbok är bara en loop över ett heltalsintervall
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;
Den loopen är också formen på en granskningspassering. Innan tusen mallar rörs vill de flesta team först veta hur många av dem som faktiskt bär makron och vad de makrona refererar till, vilket är scenariot bakom arbetsboksgranskning och konverteringsverktyget — samma Project.Count som driver en omskrivningsloop här blir en per-fil-makroräkning där
Inuti MS-OVBA-komprimeringsbehållaren
MS-OVBA:s komprimeringsformat paketerar källbyten till vad specifikationen kallar en CompressedContainer: en enda signaturbyte, som måste vara lika med 0x01, följd av en sekvens av CompressedChunk-block, var och en täcker upp till 4096 byte dekomprimerad data. Ett 16-bitars chunk-huvud bär tre fält — en 3-bitars signatur som måste vara lika med 3, ett 12-bitars storleksfält, och en CompressedChunkFlag-bit som markerar om chunkens payload är bokstavliga byte eller en tokenkomprimerad sekvens. När flaggan är satt är payloaden en serie flaggbyte-prefixade grupper om åtta tokens, och varje token är antingen en enda bokstavlig byte eller en CopyToken: en offset/längd-bakåtreferens till byte som redan dekomprimerats tidigare i samma chunk, med bitbredden fördelad mellan offset och längd som skiftar beroende på hur långt in i chunken dekomprimeraren för närvarande befinner sig. Denna del av MS-OVBA (§2.4.1, Compression and Decompression) är där en handbyggd implementation oftast förlorar en dag på ett off-by-one-fel i den bitbreddsberäkningen
Varför HotXLS skriver råa chunkar istället för att matcha tokens
HotXLS skrivväg kringgår tokenmatchningshalvan av den algoritmen helt. När den återkomprimerar en redigerad modul går varje chunk ut med CompressedChunkFlag rensad, vilket betyder att chunken innehåller bokstavliga byte snarare än bakåtreferenstokens — lagligt enligt MS-OVBA, eftersom en komprimerad behållare får bestå helt av okomprimerade chunkar, och det tar bort exakt den del av algoritmen som är svårast att få rätt för hand: att hitta giltiga bakåtreferenser och packa ett offset/längd-par in i en bitbredd som beror på den aktuella positionen inuti chunken. Avvägningen visar sig i filstorlek, inte korrekthet — en omskriven modulström hamnar nära storleken på sin källtext plus ett tvåbyte-huvud per 4096-byte-block, inte mindre som en fullt tokenkomprimerad chunk skulle vara. Varje läsare som implementerar dekomprimeringssidan av specifikationen, Excel inräknat, öppnar fortfarande resultatet korrekt, eftersom en rå chunk är precis lika giltig som en CompressedChunk som en tokenkomprimerad
Vad HotXLS lämnar orört när den skriver om en modul
Återkomprimering ersätter bara en del av modulströmmen. Varje modulström lagrar sitt prestandacache först och sin komprimerade källkod därefter, och projektets dir-ström registrerar exakt var den delningen faller för varje modul i en MODULEOFFSET-post; HotXLS läser den offseten, behåller varje byte innan den exakt som den hittade dem, och bygger om bara den komprimerade behållaren från offseten och framåt
Källtexten själv går tur och retur genom VBA-projektets egen kodsida snarare än UTF-8 — samma äldre kodsida Office skrev projektet med från början. En SourceCode-redigering som introducerar tecken utanför den kodsidans repertoar ersätts tyst med bäst-passande ersättningstecken när HotXLS kodar om strängen tillbaka till byte, avvisas inte, så ett ovanligt regionalt tecken tappat i en kommentar eller strängliteral är den mest sannolika platsen att märka förlusten. Externa referenser och biblioteksbindningar inom samma projekt följer en relaterad men separat bevarandeväg, som täcks i följeartikeln om bevarande av externa VBA-länkar, och den är värd att läsa innan en omskrivningspassering rör ett projekt som länkar ut till andra arbetsböcker eller typbibliotek
Hur får man tillbaka de omskrivna makrona i en arbetsbok?
Inget anropar återkomprimeringssteget explicit — det körs automatiskt i det ögonblick en arbetsbok eller ett fristående VBA-projekt sparas. TXLSVBAProject.ApplyChanges går igenom varje modul, återkomprimerar de vars SourceCode ändrats sedan senaste sparningen, och skriver om bara den modulens ström; den klassiska TXLSWorkbook.SaveAs, när sparmålet behåller filens ursprungliga format, och OOXML-TXLSXWorkbook.SaveAs för ett makroaktiverat XLSM-paket anropar båda den internt innan något skrivs till disk, och SaveVBAProjectToFile anropar samma metod när målet är en frikopplad VBA-projektfil snarare än en full arbetsbok
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;
Alla tre destinationerna delar samma SourceCode- och ApplyChanges-mekanik under ytan; den enda verkliga skillnaden mellan dem är vilket sparanrop som slutar utlösa återkomprimeringen
Var det fortfarande går sönder
Två felmönster är vanliga nog att planera kring innan en omskrivningspassering körs mot produktionsfiler. Ett digitalt signerat VBA-projekt slutar vara giltigt signerat i samma ögonblick dess källkod ändras, eftersom signaturen täcker projektets innehåll; HotXLS har inget sätt att signera om ett projekt åt dig, och Excel tar bort eller flaggar signaturen nästa gång filen öppnas, så ett signerat makroprojekt behöver ett steg för omsignering nedströms om den signaturen är något ditt arbetsflöde faktiskt kontrollerar. Det andra felmönstret tillhör den som frestas att återimplementera det här komprimeringsformatet från grunden istället för att använda ett bibliotek som redan hanterar det: en enda fel bit i ett chunk-huvud, i signaturnibblen, storleksfältet, eller den komprimerade flaggan, producerar en fil Excel vägrar öppna, vanligtvis bakom en generisk korruptionsvarning som inte ger någon ledtråd om vilken byte som var fel — precis den felkategori den råa chunk-skrivstrategin som beskrevs tidigare finns för att undvika
Inget av detta kräver att man reverse-engineerar formatet för att använda det. Delphi- och C++Builder-utvecklare får läs- och skrivåtkomst till SourceCode, MS-OVBA-kompatibel återkomprimering, och alla tre skriv-tillbaka-destinationer som beskrivs här som en del av standard-HotXLS-komponenten, tillsammans med resten av dess klassiska XLS- och OOXML-arbetsboks-API