At omdøbe en hardkodet regnearksreference på tværs af tusind makro-aktiverede rapportskabeloner udelukker at åbne hver fil i VBA-editoren i hånden. HotXLS, den native Delphi- og C++Builder-Excel-komponent, håndterer det tilfælde ved at eksponere et VBA-moduls kildekode som en redigerbar SourceCode-egenskab og genkomprimere hver redigering med MS-OVBA-komprimeringsalgoritmen, Microsoft definerer specifikt til VBA-lagring, og skriver resultatet tilbage til klassisk XLS-VBA-lagring, en selvstændig VBA-projektfil, eller en makro-aktiveret XLSM-projektmappe. Ingen Excel-instans, ingen VBA-editor og ingen makro-optager er involveret noget sted i den vej
Hvorfor en VBA-modulstream ikke er en tekstfil
Et VBA-modul inde i en XLS-projektmappe eller en selvstændig VBA-projektfil er ikke kildetekst, der sidder i en stream og venter på at blive læst — det er en lille binær container. En kompileret ydeevne-cache kommer først, de bytes Office bruger til at springe genkompilering af modulet over ved indlæsning, når cachen stadig matcher værtsversionen, og den faktiske kildetekst følger, kørt gennem et proprietært komprimeringsskema, MS-OVBA definerer specifikt til VBA-lagring. Det skema er ikke zip, ikke deflate, og ikke noget Windows-komprimerings-API'erne producerer nativt, hvilket er præcis, hvorfor de fleste tredjeparts-Excel-biblioteker kan læse et moduls kildekode — dekomprimering er den lettere halvdel af problemet — mens de stopper før at skrive den tilbage, da genkomprimering er, hvor en subtilt forkert bit producerer en fil, Excel nægter at åbne. Offentlige gennemgange af læse-siden findes; skrive-side-implementeringer der rent faktisk udfører genkomprimering, frem for bare at udpakke et eksisterende modul til inspektion, er sparsomme nok til, at dette forbliver et af de mindst dokumenterede hjørner af Excel-filformaterne
Hvad ændrer HotXLS' SourceCode-egenskab rent faktisk?
HotXLS repræsenterer hvert VBA-modul som et TXLSVBAModule-objekt med en almindelig SourceCode: WideString-egenskab, og at tildele den en ny værdi er nøjagtig så simpelt, som det ser ud: modulet markeres beskidt i hukommelsen, og intet rører den underliggende OLE-stream, før projektet gemmes. Selve projektet kommer fra IXLSWorkbook.VBAProject på den klassiske XLS-motor eller TXLSXWorkbook.ParsedVBAProject på den OOXML-makro-aktiverede motor, begge returnerer et TXLSVBAProject, hvis moduler sidder bag en 1-baseret Item[]-indekser og en Count-egenskab, så en batch-redigering på tværs af hvert modul i en projektmappe er bare en løkke over et heltalsinterval
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 løkke er også formen på et audit-gennemløb. Før tusind skabeloner bliver rørt, vil de fleste teams først vide, hvor mange af dem der rent faktisk bærer makroer, og hvad de makroer refererer, hvilket er scenariet bag projektmappe-audit- og konverterings-workbenchen — den samme Project.Count, der driver en omskrivnings-løkke her, bliver en per-fil-makro-optælling der
Inde i MS-OVBA-komprimeringscontaineren
MS-OVBA's komprimeringsformat pakker kildebytes ind i, hvad specifikationen kalder en CompressedContainer: en enkelt signaturbyte, krævet at være lig 0x01, efterfulgt af en sekvens af CompressedChunk-blokke, hver dækker op til 4096 bytes af dekomprimeret data. En 16-bit chunk-header bærer tre felter — en 3-bit-signatur der skal være lig 3, et 12-bit størrelsesfelt, og en CompressedChunkFlag-bit der markerer, om chunkens payload er bogstavelige bytes eller en token-komprimeret sekvens. Når flaget er sat, er payloaden en serie af flag-byte-prefikserede grupper af otte tokens, og hver token er enten en enkelt bogstavelig byte eller en CopyToken: en forskydning/længde-tilbagereference ind i bytes allerede dekomprimeret tidligere i den samme chunk, med bitbredden delt mellem forskydning og længde, der skifter afhængigt af, hvor langt ind i chunken dekomprimeringen aktuelt sidder. Denne del af MS-OVBA (§2.4.1, Compression and Decompression) er, hvor en håndrullet implementering oftest mister en dag på en fejl-med-én i den bitbredde-beregning
Hvorfor HotXLS skriver rå chunks i stedet for at matche tokens
HotXLS' skrivevej omgår token-matchings-halvdelen af den algoritme helt. Når den genkomprimerer et redigeret modul, går hver chunk ud med CompressedChunkFlag ryddet, hvilket betyder at chunken holder bogstavelige bytes frem for tilbagereference-tokens — lovligt under MS-OVBA, da en komprimeret container tillades at bestå udelukkende af ikke-komprimerede chunks, og det fjerner præcis den del af algoritmen, der er sværest at få rigtig i hånden: at finde gyldige tilbagereferencer og pakke et forskydning/længde-par ind i en bitbredde, der afhænger af den nuværende position inde i chunken. Afvejningen viser sig i filstørrelse, ikke korrekthed — en omskrevet modulstream lander tæt på størrelsen af sin kildetekst plus en to-byte-header pr. 4096-byte-blok, ikke mindre på den måde en fuldt token-komprimeret chunk ville være. Hver læser, der implementerer dekomprimerings-siden af specifikationen, Excel inkluderet, åbner stadig resultatet korrekt, fordi en rå chunk er lige så gyldig en CompressedChunk som en token-komprimeret en
Hvad HotXLS lader være urørt, når den omskriver et modul
Genkomprimering erstatter kun nogensinde en del af modulstreamen. Hver modulstream gemmer sin ydeevne-cache først og sin komprimerede kildekode dernæst, og projektets dir-stream registrerer præcis, hvor det skel falder for hvert modul i en MODULEOFFSET-post; HotXLS læser den forskydning, beholder hver byte før den nøjagtig som den fandt dem, og genopbygger kun den komprimerede container fra forskydningen og fremad
Selve kildeteksten går tur-retur gennem VBA-projektets egen kodeside frem for UTF-8 — den samme legacy-kodeside Office skrev projektet med i første omgang. En SourceCode-redigering, der introducerer tegn uden for den kodesides repertoire, bliver i stilhed erstattet med best-fit-erstatningstegn, når HotXLS genkoder strengen tilbage til bytes, ikke afvist, så et usædvanligt regionalt tegn droppet ind i en kommentar eller streng-literal er det mest sandsynlige sted at bemærke tabet. Eksterne referencer og biblioteksbindinger inde i det samme projekt følger en beslægtet, men separat bevaringsvej, dækket i følgeartiklen om bevaring af eksterne VBA-links, og det er værd at læse, før et omskrivningsgennemløb rører et projekt, der linker ud til andre projektmapper eller typebiblioteker
Hvordan får man de omskrevne makroer tilbage ind i en projektmappe?
Intet kalder genkomprimeringstrinnet eksplicit — det kører automatisk, i det øjeblik en projektmappe eller et selvstændigt VBA-projekt gemmes. TXLSVBAProject.ApplyChanges gennemgår hvert modul, genkomprimerer dem hvis SourceCode ændrede sig siden sidste gemning, og omskriver kun det moduls stream; den klassiske TXLSWorkbook.SaveAs, når gemme-målet beholder filens oprindelige format, og OOXML-TXLSXWorkbook.SaveAs til en makro-aktiveret XLSM-pakke kalder begge den internt, før noget skrives til disk, og SaveVBAProjectToFile kalder den samme metode, når målet er en løsrevet VBA-projektfil frem for en fuld projektmappe
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 tre destinationer deler den samme SourceCode- og ApplyChanges-mekanik nedenunder; den eneste reelle forskel mellem dem er, hvilket gemme-kald der ender med at udløse genkomprimeringen
Hvor dette stadig går i stykker
To fejltilstande er almindelige nok til at planlægge omkring, før et omskrivningsgennemløb kører mod produktionsfiler. Et digitalt signeret VBA-projekt holder op med at være gyldigt signeret, i det øjeblik dets kildekode ændres, da signaturen dækker projektets indhold; HotXLS har ingen måde at gensignere et projekt på dine vegne, og Excel dropper eller flager signaturen, næste gang filen åbnes, så et signeret makroprojekt har brug for et gensigneringstrin nedstrøms, hvis den signatur er noget, ens workflow rent faktisk tjekker. Den anden fejltilstand tilhører alle der er fristet til at genimplementere dette komprimeringsformat fra bunden i stedet for at bruge et bibliotek, der allerede håndterer det: én forkert bit i en chunk-header, i signatur-nibblen, størrelsesfeltet eller det komprimerede flag, producerer en fil, Excel nægter at åbne, sædvanligvis bag en generisk korruptionsadvarsel, der ikke giver noget hint om, hvilken byte der var forkert — netop den klasse af bug, den rå-chunk-skrivestrategi beskrevet tidligere findes for at undgå
Intet af dette kræver reverse-engineering af formatet at bruge. Delphi- og C++Builder-udviklere får SourceCode-læse- og -skriveadgang, MS-OVBA-kompatibel genkomprimering, og alle tre tilbageskrivnings-destinationer beskrevet her som en del af standard-HotXLS-komponenten, sammen med resten af dens klassiske XLS- og OOXML-projektmappe-API