Bin adet makro etkin rapor şablonu genelinde sabit kodlanmış bir çalışma sayfası referansını yeniden adlandırmak, her dosyayı VBA düzenleyicisinde elle açmayı devre dışı bırakır. Yerel Delphi ve C++Builder Excel bileşeni olan HotXLS, bir VBA modülünün kaynağını düzenlenebilir bir SourceCode özelliği olarak sunarak ve her düzenlemeyi Microsoft'un VBA depolaması için tanımladığı MS-OVBA sıkıştırma algoritmasıyla yeniden sıkıştırarak, sonucu klasik XLS VBA deposuna, bağımsız bir VBA proje dosyasına veya makro etkin bir XLSM çalışma kitabına geri yazarak bu durumu ele alır. Bu yolun hiçbir yerinde bir Excel örneği, bir VBA düzenleyicisi veya bir makro kaydedici yer almaz
Bir VBA modülü akışı neden bir metin dosyası değildir?
Bir XLS çalışma kitabı veya bağımsız bir VBA proje dosyası içindeki bir VBA modülü, okunmayı bekleyen bir akışta oturan kaynak metin değildir -küçük bir ikili kapsayıcıdır. Önce derlenmiş bir performans önbelleği gelir; Office'in önbellek hâlâ ana bilgisayar sürümüyle eşleştiğinde yüklemede modülü yeniden derlemeyi atlamak için kullandığı baytlar; ve gerçek kaynak metin ardından gelir, MS-OVBA'nın özellikle VBA depolaması için tanımladığı özel bir sıkıştırma şeması üzerinden çalıştırılmış olarak. Bu şema zip değildir, deflate değildir ve Windows sıkıştırma API'lerinin doğal olarak ürettiği herhangi bir şey de değildir; bu, tam olarak çoğu üçüncü taraf Excel kütüphanesinin bir modülün kaynağını okuyabilmesinin -kod çözme sorunun daha kolay yarısıdır- ama onu geri yazmakta duraksamasının nedenidir, çünkü yeniden sıkıştırma, ince bir şekilde yanlış bir bitin Excel'in açmayı reddettiği bir dosya ürettiği yerdir. Okuma tarafının kamuya açık yazıları vardır; gerçekten yeniden sıkıştırmayı çalıştıran yazma tarafı uygulamaları -mevcut bir modülü inceleme için açmaktan öteye giden- yeterince nadirdir ki bu, Excel dosya formatlarının en az belgelenmiş köşelerinden biri olarak kalır
HotXLS'in SourceCode özelliği gerçekte neyi değiştirir?
HotXLS, her VBA modülünü düz bir SourceCode: WideString özelliğine sahip bir TXLSVBAModule nesnesi olarak temsil eder ve ona yeni bir değer atamak tam olarak göründüğü kadar basittir: modül bellekte kirli olarak işaretlenir ve proje kaydedilene kadar altındaki OLE akışına hiçbir şey dokunmaz. Projenin kendisi klasik XLS motorunda IXLSWorkbook.VBAProject'ten veya OOXML makro etkin motorunda TXLSXWorkbook.ParsedVBAProject'ten gelir; her ikisi de modülleri 1 tabanlı bir Item[] indeksleyicisinin ve bir Count özelliğinin arkasında oturan bir TXLSVBAProject döndürür; bu yüzden bir çalışma kitabındaki her modülde toplu bir düzenleme yalnızca bir tam sayı aralığı üzerinde bir döngüdür
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;
Bu döngü aynı zamanda bir denetim geçişinin de şeklidir. Bin şablona dokunulmadan önce, çoğu ekip önce bunlardan kaçının gerçekten makro taşıdığını ve bu makroların neye referans verdiğini bilmek ister ki bu, çalışma kitabı denetimi ve dönüştürme çalışma tezgahının arkasındaki senaryodur -burada bir yeniden yazma döngüsünü yönlendiren aynı Project.Count, orada dosya başına bir makro sayımına dönüşür
MS-OVBA sıkıştırma kapsayıcısının içinde
MS-OVBA'nın sıkıştırma formatı, kaynak baytları spesifikasyonun CompressedContainer dediği şeye paketler: 0x01'e eşit olması gereken tek bir imza baytı, ardından her biri sıkıştırılmış verinin en fazla 4096 baytını kapsayan bir dizi CompressedChunk bloğu. 16 bitlik bir yığın başlığı üç alan taşır -3'e eşit olması gereken 3 bitlik bir imza, 12 bitlik bir boyut alanı ve yığının yükünün gerçek baytlar mı yoksa token sıkıştırılmış bir dizi mi olduğunu işaretleyen bir CompressedChunkFlag biti. Bayrak ayarlandığında, yük, bayrak baytı önekli sekiz token gruplarından oluşan bir çalışmadır ve her token ya tek bir gerçek bayt ya da bir CopyToken'dır: aynı yığın içinde daha önce zaten kod çözülmüş baytlara olan bir konum/uzunluk geri referansı; konum ve uzunluk arasındaki bit genişliği bölünmesi, kod çözücünün şu anda yığın içinde ne kadar ileride olduğuna bağlı olarak değişir. MS-OVBA'nın bu kısmı (§2.4.1, Sıkıştırma ve Kod Çözme), elle yazılmış bir uygulamanın en çok o bit genişliği hesaplamasındaki bir off-by-one hatasına bir gün kaybettiği yerdir
HotXLS token eşleştirme yerine neden ham yığınlar yazıyor?
HotXLS'in yazma yolu, bu algoritmanın token eşleştirme yarısını tamamen atlar. Düzenlenmiş bir modülü yeniden sıkıştırdığında, her yığın CompressedChunkFlag temizlenmiş olarak çıkar; bu, yığının geri referans tokenleri yerine gerçek baytlar tuttuğu anlamına gelir -MS-OVBA altında yasaldır, çünkü sıkıştırılmış bir kapsayıcının tamamen sıkıştırılmamış yığınlardan oluşmasına izin verilir- ve bu, algoritmanın elle doğru yapılması en zor kısmını tam olarak ortadan kaldırır: geçerli geri referanslar bulmak ve bir konum/uzunluk çiftini yığın içindeki mevcut konuma bağlı bir bit genişliğine paketlemek. Ödünleşim doğrulukta değil dosya boyutunda ortaya çıkar -yeniden yazılmış bir modül akışı, kaynak metninin boyutuna 4096 baytlık blok başına iki baytlık bir başlık eklenmiş haline yakın çıkar, tamamen token sıkıştırılmış bir yığının olacağı gibi daha küçük değil. Spesifikasyonun kod çözme tarafını uygulayan her okuyucu, Excel dahil, sonucu yine de doğru şekilde açar, çünkü ham bir yığın, token sıkıştırılmış bir yığın kadar geçerli bir CompressedChunk'tır
HotXLS bir modülü yeniden yazarken neye dokunmadan bırakır?
Yeniden sıkıştırma yalnızca modül akışının bir kısmını değiştirir. Her modül akışı önce performans önbelleğini, sonra sıkıştırılmış kaynağını saklar ve projenin dir akışı, her modül için bu ayrımın tam olarak nereye düştüğünü bir MODULEOFFSET girdisinde kaydeder; HotXLS bu konumu okur, ondan önceki her baytı bulduğu gibi tam olarak korur ve yalnızca sıkıştırılmış kapsayıcıyı o konumdan itibaren yeniden oluşturur
Kaynak metnin kendisi, UTF-8 yerine VBA projesinin kendi kod sayfası üzerinden gidip gelir -Office'in projeyi ilk yazdığı aynı eski kod sayfası. O kod sayfasının kapsamı dışında karakterler getiren bir SourceCode düzenlemesi, HotXLS dizeyi baytlara geri kodladığında reddedilmez, sessizce en iyi uyum yerine koyma karakterleriyle değiştirilir; bu yüzden bir yoruma veya dize değişmezine düşürülen alışılmadık bir bölgesel karakter, kaybı fark etmenin en olası yeridir. Aynı proje içindeki dış referanslar ve kütüphane bağlamaları, VBA dış bağlantı korunması üzerine tamamlayıcı makalede ele alınan ilgili ama ayrı bir koruma yolunu izler ve bir yeniden yazma geçişi başka çalışma kitaplarına veya tür kütüphanelerine bağlanan bir projeye dokunmadan önce okumaya değer
Yeniden yazılan makroları bir çalışma kitabına nasıl geri alırsınız?
Hiçbir şey yeniden sıkıştırma adımını açıkça çağırmaz -bir çalışma kitabı veya bağımsız bir VBA projesi kaydedildiği anda otomatik olarak çalışır. TXLSVBAProject.ApplyChanges, her modülü dolaşır, son kaydetmeden bu yana SourceCode'u değişenleri yeniden sıkıştırır ve yalnızca o modülün akışını yeniden yazar; klasik TXLSWorkbook.SaveAs, kayıt hedefi dosyanın orijinal formatını koruduğunda, ve makro etkin bir XLSM paketi için OOXML TXLSXWorkbook.SaveAs, ikisi de diske hiçbir şey yazılmadan önce bunu dahili olarak çağırır ve SaveVBAProjectToFile, hedef tam bir çalışma kitabı değil ayrık bir VBA proje dosyası olduğunda aynı metodu çağırır
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;
Üç hedefin tümü altında aynı SourceCode ve ApplyChanges mekaniğini paylaşır; aralarındaki tek gerçek fark, hangi kaydetme çağrısının yeniden sıkıştırmayı tetiklediğidir
Bu hâlâ nerede bozuluyor?
Üretim dosyalarına karşı bir yeniden yazma geçişi çalıştırılmadan önce planlanacak kadar yaygın iki başarısızlık modu vardır. Dijital olarak imzalanmış bir VBA projesi, kaynağı değiştiği anda geçerli şekilde imzalanmış olmaktan çıkar, çünkü imza projenin içeriğini kapsar; HotXLS'in sizin adınıza bir projeyi yeniden imzalamasının bir yolu yoktur ve Excel, dosya bir sonraki açıldığında imzayı düşürür veya işaretler; bu yüzden imzalı bir makro projesi, bu imza iş akışınızın gerçekten kontrol ettiği bir şeyse aşağı akışta bir yeniden imzalama adımına ihtiyaç duyar. İkinci başarısızlık modu, bu sıkıştırma formatını, onu zaten ele alan bir kütüphane kullanmak yerine sıfırdan yeniden uygulamaya kalkışan herkese aittir: bir yığın başlığındaki -imza yarım baytında, boyut alanında veya sıkıştırılmış bayrakta- tek bir yanlış bit, Excel'in açmayı reddettiği, genellikle hangi baytın yanlış olduğuna dair hiçbir ipucu vermeyen genel bir bozulma uyarısının arkasında duran bir dosya üretir -bu, tam olarak daha önce açıklanan ham-yığın yazma stratejisinin kaçınmak için var olduğu hata sınıfıdır
Bunların hiçbiri kullanmak için formatı tersine mühendislik yapmayı gerektirmez. Delphi ve C++Builder geliştiricileri, standart HotXLS Bileşeni'nin bir parçası olarak SourceCode okuma ve yazma erişimini, MS-OVBA uyumlu yeniden sıkıştırmayı ve burada açıklanan üç geri yazma hedefinin tümünü, klasik XLS ve OOXML çalışma kitabı API'sinin geri kalanının yanında elde eder