Teknisk artikel

HotXLS: bevara VBA-makron och externa länkar i Delphi

Tänk dig ett jobb som gör nästan ingenting: öppna en månadsarbetsbok, skriv in dagens datum i en cell, spara tillbaka den. Kör det genom en tjänst tillräckligt ofta och ett klagomål kommer ändå. Makrona är borta, eller de länkade växelkurserna läser nu #REF!, och driftteamet är övertygat om att din kod raderade dem. Den raderade ingenting. Det som vanligtvis hände är att en makroaktiverad arbetsbok gick ut under ett vanligt .xlsx-namn, och Excel lydde innehållstypsreglerna i ECMA-376: ett paket vars innehållstyp inte deklarerar någon VBA kan inte ladda ett VBA-projekt, oavsett om byten ligger där. Filen gick inte sönder. Den döptes om till ett tillstånd där Excel är skyldigt att ignorera en del av den

Makron och externa arbetsbokslänkar är de två saker automation tappar mest pålitligt, av samma underliggande skäl. Båda bor utanför det cellrutnät som redigeringskoden faktiskt rör, så kod som resonerar i termer av rader och kolumner släpper dem utan att någonsin utfärda en radering. HotXLS är ett inbyggt Delphi- och C++Builder-bibliotek som läser och skriver XLS och XLSX utan installerat Excel, och det behandlar båda tillgångarna som laster det bär med avsikt snarare än data det råkar kopiera. Det som följer är vad var och en av dem behöver av din spar-väg, och var garantierna tar slut

Varför de här två tillgångarna beter sig olika vid en omskrivning

Ett VBA-projekt är en enda ogenomskinlig binär. I ett OOXML-paket är det filen vbaProject.bin; i en äldre BIFF-fil är det en OLE-lagring. Det finns exakt två sätt att förlora det: skrivaren kopierar det aldrig till utdatan, eller så får utdatan en filtyp som förbjuder det. Endera misslyckandet är totalt och tyst. Projektet finns eller inte

En extern länk är ingen blob alls. Den är en liten graf av relationer: en målsökväg eller URL som pekar på en annan arbetsbok, listan över bladnamn det målet exponerar, och en valfri cache av de värden som senast sågs i de bladen så att Excel kan visa något när målet är offline. De tre delarna har olika livslängd under en omskrivning, och ett bibliotek kan troget bevara några medan det tyst släpper andra. Den asymmetrin är den del som är värd att vara precis om, eftersom inget i cellredigeringskoden lyfter fram den

Jämförelsediagram av en VBA-projektblob mot de tre delarna av en extern arbetsbokslänk som HotXLS bär genom en omskrivning i Delphi
Ett VBA-projekt överlever en omskrivning som binär last enligt allt-eller-inget, medan en extern länk är en liten graf vars mål, bladnamn och cachelagrade värden kan bevaras eller släppas oberoende av varandra

Att bära ett VBA-projekt genom en XLSX-omskrivning

På XLSX-sidan behåller TXLSXWorkbook makrolasten ordagrant. Egenskapen VbaProject håller de råa vbaProject.bin-byten i en AnsiString, och en tom sträng är hur modellen säger att det inte finns några makron. Runt den sitter tre operationer: HasVbaProject svarar på om ett projekt finns, ClearVbaProject tar bort det med avsikt, och LoadVbaProjectFromFile injicerar ett som extraherats ur en mall. Det sista anropet är värt mer än det ser ut. Det låter genererade arbetsböcker plocka upp ett standardmakroprojekt utan att släpa en fullständig mallfil genom pipelinen

Flödesdiagram över ett spar-anrop i Delphi där filändelsen .xlsm väljer den makroaktiverade innehållstypen och .xlsx får Excel att tyst vägra makrona
HotXLS bär de råa vbaProject.bin-byten genom sparandet, och filändelsen .xlsm är det som väljer den makroaktiverade innehållstyp Excel kräver
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Data');
    Sheet.Cells[1, 1].Value := 'Refreshed ' + DateTimeToStr(Now);

    Book.LoadVbaProjectFromFile('macros\vbaProject.bin');
    if not Book.HasVbaProject then
      raise Exception.Create('VBA payload failed to load');

    // Filändelsen .xlsm är inte kosmetisk: den väljer den
    // makroaktiverade innehållstypen inuti paketet.
    Book.SaveAs('monthly-report.xlsm');
  finally
    Book.Free;
  end;
end;

Spar-raden är där hela problemet vänder. En arbetsbok som håller ett VBA-projekt måste skrivas med makroaktiverad semantik, och HotXLS tillämpar den när målnamnet slutar på .xlsm. Ge den .xlsx istället och Excel vägrar makrona, trots att byten fysiskt finns i paketet och skulle deserialiseras felfritt. Filändelsen är ingen utsmyckning; den väljer den innehållstyp som säger åt Excel att ett VBA-projekt får finnas. För det mesta behöver du bara bära lasten igenom. När du behöver läsa in i den, säg för att lista modulnamn till en granskningsrapport, exponerar ParsedVBAProject en tolkad modulmodell medan VbaProject förblir de ursprungliga orörda byten

Att återanvända makron från äldre XLS-arbetsböcker

BIFF-fasaden speglar den verktygsuppsättningen med ett extra steg. HasVBAProject sonderar en inläst fil, SaveVBAProjectToFile skriver ut projektlagringen till disk, och LoadVBAProjectFromFile läser tillbaka en in i en annan arbetsbok. Omvägen via en fil gör en vanlig moderniseringssyssla enkel: lyft ut makrona ur en modell från 2003 och plantera in dem i nygenererad XLS-utdata, utan att någon originalmall behövs vid körning

var
  Src, Dst: IXLSWorkbook;   // gränssnittsreferenser: ingen manuell Free
begin
  Src := TXLSWorkbook.Create;
  if Src.Open('legacy-model.xls') <= 0 then
    raise Exception.Create('Cannot open legacy model');
  if Src.HasVBAProject then
    Src.SaveVBAProjectToFile('extracted-vba.bin');

  Dst := TXLSWorkbook.Create;
  Dst.Sheets.Add.Name := 'Report2026';
  Dst.LoadVBAProjectFromFile('extracted-vba.bin');
  Dst.SaveAs('report-with-macros.xls');
end;

Minnesmodellen är fällan här, och den går motsatt vägen mot XLSX-klassen. TXLSWorkbook hålls genom det referensräknade gränssnittet IXLSWorkbook, så du frigör den aldrig för hand; XLSX-klassen TXLSXWorkbook är ett vanligt objekt du måste linda i try..finally och frigöra. Blanda de två konventionerna i en enhet så följer krascher av dubbelfrigöring. Ännu en gräns värd att respektera: håll extraktion och injektion inom ett och samma filformat. BIFF-projektlagringen och OOXML:s vbaProject.bin är kusiner, inte samma behållare, och en pipeline som måste avge makron i båda formaten bör hålla en separat makromall för vardera

Externa länkar: kartan överlever, de cachelagrade värdena inte

För XLSX-arbetsböcker exponerar HotXLS externa länkar genom samlingen ExternalLinks. Varje TXLSXExternalLink bär ett Target, den fjärranslutna arbetsbokens sökväg eller URL, plus en SheetNames-lista som namnger bladen den refererar till. Båda överlever en cykel av öppning och sparande intakta, och du kan också bygga en länk från grunden:

var
  Link: TXLSXExternalLink;
begin
  Link := Book.ExternalLinks.Add('\\fileserver\finance\fx-rates-2026.xlsx');
  Link.SheetNames.Add('FX');

  if Book.ExternalLinks.Count > 0 then
    Writeln(Format('%d external link(s): delivery requires reachable targets',
      [Book.ExternalLinks.Count]));
end;

Gränsen sitter en nivå djupare än mållistan. HotXLS tar länkkartan på rundtur, alltså målet och bladnamnen, men den tolkar eller skriver inte om de cachelagrade cellvärden som OOXML håller i länkens sheetDataSet-element. Den cachen är vad som låter Excel visa ett senast känt tal när källfilen är offline, och en genererad arbetsbok levereras utan den. Följden landar hos mottagaren, inte hos dig. Öppna en sådan fil där målet inte går att nå, en bärbar dator utanför VPN eller en filutdelning som döpts om, och formlerna som beror på länken löses upp till #REF! eller fastnar bakom en uppdateringsfråga. Så två regler faller ut av det här. Lova inte att en genererad arbetsbok visar sina externt länkade värden offline. Och läs ett ExternalLinks.Count skilt från noll som ett leveransvillkor snarare än en funktion: varje mål måste gå att nå från den plats där filen faktiskt kommer att öppnas

Diagram över vilka delar av en extern HotXLS-arbetsbokslänk som överlever en omskrivning och vad som händer när de cachelagrade värdena bakom sheetDataSet saknas offline
HotXLS tar länkmålet och dess bladnamn på rundtur, men de cachelagrade cellvärdena bakom sheetDataSet förs inte in i den genererade filen

Vad XLS-läsaren bevarar byte för byte

För strukturer den inte modellerar har BIFF-sidan ett annat svar: lämna dem exakt som de hittades. Pivotcacher och pivotvyer (postfamiljen SX*), QueryTable-definitioner, externa dataanslutningar, egna vyer, sidhuvudsbilder och temaposter passerar alla genom en cykel av öppning och sparande som råa postblock, otolkade och oförändrade. Externa referenser själva tar rundturen genom de underliggande posterna EXTERNSHEET och SupBook. Det finns inget typat skapande-API för dem på XLS-sidan, men en befintlig länk överlever redigering orörd

Bevarande byte för byte är en genuin garanti med en vass kant. Eftersom ingenting läser en bevarad struktur kan dina redigeringar inte korrumpera den. Av samma skäl uppdaterar ingenting den heller. Infoga rader genom ett område som en bevarad pivotcache eller frågetabell pekar på, och strukturen håller sina ursprungliga koordinater medan datan under förskjuts. Filen är fortfarande giltig XML eller BIFF; meningen har tyst glidit ur linje, och inget fel utlöses för att berätta det. Den försvarbara layouten är att hålla genererade redigeringar på blad som inte håller några bevarade strukturer, vilket är samma disciplin som skyddar låsta och utskriftskonfigurerade blad i vår artikel om kalkylbladsskydd och sidformat

Att verifiera filen du faktiskt skrev

Båda felbeteendena är tysta vid skrivning, så den assertion som spelar roll görs genom att öppna utdatan igen snarare än genom att lita på koden som framställde den. Tre kontroller täcker nästan allt. Öppna filen igen och bekräfta att HasVbaProject fortfarande returnerar sant närhelst makron förväntades, vilket fångar både en tappad last och en felaktig filändelse i ett enda test. Läs ExternalLinks.Count och jämför det mot antalet före omskrivningen. Öppna sedan filen en gång i Excel med makron avstängda, eftersom Excels validering av innehållstyp är strängare än något biblioteks, och Excel är programmet dina kunder kommer att döma filen efter

Inget av det kräver en fullständig tolkning på vägen in. När arbetsböcker anländer i volym och du bara behöver sortera vilka som bär styrt innehåll, låter den lättviktiga sonderingen i vår artikel om bladlistning och lättviktig arbetsboksinspektion dig dirigera filer med makron och länkar in i en strängare pipeline innan den första omskrivningen ens körs

Några frågor kommer upp tillräckligt ofta för att besvaras direkt. HotXLS kör aldrig de makron det bevarar: det finns ingen VBA-körtid i biblioteket, bara maskineriet för att lagra, kopiera, extrahera och injicera projektet som data. På en server är det en säkerhetsegenskap värd att uttala, eftersom ett fientligt makro som passerar genom pipelinen förblir inert tills ett skrivbords-Excel öppnar filen och en användare aktiverar innehållet. Att konvertera en .xlsm till .xlsx och behålla makrona går inte, och det är formatets regel snarare än en biblioteksbegränsning: innehållstypen .xlsx deklarerar en makrofri arbetsbok, så de enda ärliga utfallen är att stanna vid .xlsm eller att anropa ClearVbaProject och leverera en fil som verkligen inte har några. Den tysta omdöpningen är det enda valet som inte tillfredsställer någon. Och när länkade celler visar #REF! efter en omskrivning är orsaken den saknade värdecachen som diskuterades ovan: den nya filen bär målet men inte de cachelagrade talen, så Excel måste lösa upp källan vid öppning, och en onåbar eller miljöberoende sökväg omintetgör det. Antingen garanterar du att målet går att nå, eller så skriver du beräknade värden in i cellerna före leverans och släpper beroendet helt

Att redigera andras arbetsböcker handlar mest om att bevara saker du inte skrev och inte helt förstår. De faciliteter för rundtur med VBA och externa länkar som beskrivs här levereras med HotXLS Delphi Component för Delphi och C++Builder, tillsammans med de granskningsegenskaper som låter dig upptäcka styrt innehåll i samma stund en fil anländer