מאמר טכני

שימור מאקרו VBA וקישורים חיצוניים כאשר קוד Delphi כותב מחדש חוברת עבודה

שקלו עבודה שכמעט אינה עושה דבר: פתחו חוברת עבודה חודשית, כתבו את תאריך היום אל תא אחד, שמרו אותה בחזרה. הריצו זאת דרך שירות בתדירות גבוהה מספיק ותלונה תגיע בכל זאת. המאקרו נעלם, או ששערי החליפין המקושרים מציגים כעת #REF!, וצוות התפעול משוכנע שהקוד שלכם מחק אותם. הוא לא מחק דבר. מה שקרה בדרך כלל הוא שחוברת עבודה עם מאקרו מופעל יצאה תחת שם .xlsx רגיל, ו-Excel ציית לכללי סוג התוכן של ECMA-376: חבילה שסוג התוכן שלה אינו מצהיר על VBA אינה יכולה לטעון פרויקט VBA, ללא קשר לשאלה אם הבייטים יושבים שם בדיוק. הקובץ לא נשבר. הוא שונה שמו אל מצב שבו Excel מחויב להתעלם מחלק ממנו

מאקרו וקישורי חוברת עבודה חיצוניים הם שני הדברים שאוטומציה מאבדת בצורה האמינה ביותר, מאותה סיבה בסיסית. שניהם חיים מחוץ לרשת התאים שקוד עריכה למעשה נוגע בה, ולכן קוד שמנמק במונחי שורות ועמודות יישמיט אותם מבלי להוציא אי-פעם פקודת מחיקה. HotXLS היא ספרייה מקורית ל-Delphi ול-C++Builder שקוראת וכותבת XLS ו-XLSX ללא Excel מותקן, והיא מתייחסת לשני הנכסים כמטענים שהיא נושאת במכוון ולא כנתונים שהיא במקרה מעתיקה. מה שלהלן הוא מה שכל אחד מהם צריך ממסלול השמירה שלכם, והיכן הערבויות נעצרות

מדוע שני הנכסים האלה מתנהגים שונה תחת כתיבה מחדש

פרויקט VBA הוא בינארי אטום אחד. בחבילת OOXML הוא הקובץ vbaProject.bin; בקובץ BIFF ישן הוא אחסון OLE. יש בדיוק שתי דרכים לאבד אותו: הכותב לעולם אינו מעתיק אותו אל הפלט, או שהפלט מקבל סוג קובץ שאוסר אותו. כל אחד מהכשלים הוא מוחלט ושקט. הפרויקט נוכח או אינו נוכח

קישור חיצוני אינו בלוב כלל. הוא גרף קטן של קשרים: נתיב יעד או כתובת URL המצביעים אל חוברת עבודה אחרת, רשימת שמות הגיליונות שאותו יעד חושף, ומטמון אופציונלי של הערכים שנראו לאחרונה באותם גיליונות כדי ש-Excel יוכל להציג משהו כשהיעד במצב לא מקוון. לשלושת החלקים האלה אורך חיים שונה תחת כתיבה מחדש, וספרייה יכולה לשמר חלקם בנאמנות בעוד היא משמיטה אחרים בשקט. אי-הסימטריה הזו היא החלק שכדאי לדייק בו, מפני ששום דבר בקוד עריכת התאים לא יחשוף אותה

נשיאת פרויקט VBA דרך כתיבה מחדש של XLSX

בצד ה-XLSX, TXLSXWorkbook שומרת את מטען המאקרו מילה במילה. המאפיין VbaProject מחזיק את בייטי ה-vbaProject.bin הגולמיים בתוך AnsiString, ומחרוזת ריקה היא הדרך שבה המודל אומר שאין מאקרו. סביבו יושבות שלוש פעולות: HasVbaProject עונה אם פרויקט נוכח, ClearVbaProject מסיר אותו בכוונה, ו-LoadVbaProjectFromFile מזריק אחד שחולץ מתבנית. הקריאה האחרונה הזו שווה יותר ממה שהיא נראית. היא מאפשרת לחוברות עבודה מחוללות לקלוט פרויקט מאקרו סטנדרטי מבלי לגרור קובץ תבנית מלא דרך הצינור

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');

    // The .xlsm extension is not cosmetic: it selects the
    // macro-enabled content type inside the package.
    Book.SaveAs('monthly-report.xlsm');
  finally
    Book.Free;
  end;
end;

שורת השמירה היא המקום שבו כל הבעיה מתהפכת. חוברת עבודה המחזיקה פרויקט VBA חייבת להיכתב עם סמנטיקת מאקרו מופעל, ו-HotXLS מחילה אותה כאשר שם היעד מסתיים ב-.xlsm. תנו לה .xlsx במקום ו-Excel ידחה את המאקרו, גם אם הבייטים נוכחים פיזית בחבילה ויפענחו בסדר גמור. הסיומת אינה קישוט; היא בוחרת את סוג התוכן שאומר ל-Excel שמותר לפרויקט VBA להתקיים. רוב הזמן אתם רק צריכים לשאת את המטען דרך. כשאתם צריכים לקרוא לתוכו, נניח כדי לרשום שמות מודולים עבור דוח ביקורת, ParsedVBAProject חושף מודל מודולים מנותח בעוד VbaProject נשאר הבייטים המקוריים שלא נגעו בהם

שימוש חוזר במאקרו מחוברות עבודה XLS ישנות

מעטפת ה-BIFF משקפת את ערכת הכלים הזו עם צעד נוסף אחד. HasVBAProject בודקת קובץ טעון, SaveVBAProjectToFile כותבת את אחסון הפרויקט אל הדיסק, ו-LoadVBAProjectFromFile קוראת אחד בחזרה אל חוברת עבודה אחרת. המעקף דרך קובץ הופך מטלת מודרניזציה נפוצה לפשוטה: הרימו את המאקרו ממודל מתקופת 2003 ושתלו אותו אל פלט XLS שזה עתה חולל, ללא צורך בתבנית מקורית בזמן ריצה

var
  Src, Dst: IXLSWorkbook;   // interface references: no manual 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;

מודל הזיכרון הוא המלכודת כאן, והוא פועל הפוך ממחלקת ה-XLSX. TXLSWorkbook מוחזק דרך ממשק IXLSWorkbook סופר-ההפניות, ולכן לעולם אינכם משחררים אותו ביד; ה-XLSX TXLSXWorkbook הוא אובייקט רגיל שעליכם לעטוף ב-try..finally ולשחרר. ערבבו את שתי המוסכמות ביחידה אחת וקריסות שחרור-כפול יבואו בעקבותיהן. גבול נוסף שכדאי לכבד: שמרו חילוץ והזרקה בתוך פורמט קובץ יחיד. אחסון פרויקט ה-BIFF וה-vbaProject.bin של OOXML הם בני-דודים, לא אותו מכל, וצינור שחייב לפלוט מאקרו בשני הפורמטים צריך לשמור תבנית מאקרו נפרדת לכל אחד

קישורים חיצוניים: המפה שורדת, הערכים שבמטמון לא

עבור חוברות עבודה XLSX, HotXLS חושפת קישורים חיצוניים דרך אוסף ExternalLinks. כל TXLSXExternalLink נושא Target, הנתיב או כתובת ה-URL של חוברת העבודה המרוחקת, בתוספת רשימת SheetNames המנקבת בשם הגיליונות שהוא מפנה אליהם. שניהם שורדים מחזור פתיחה-ושמירה ללא פגיעה, ואתם יכולים גם לבנות קישור מאפס:

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;

הגבול יושב רמה אחת עמוק יותר מרשימת היעדים. HotXLS עוברת סבב על מפת הקישורים, כלומר היעד ושמות הגיליונות, אך היא אינה מנתחת או כותבת מחדש את ערכי התאים שבמטמון ש-OOXML שומר באלמנט sheetDataSet של הקישור. המטמון הזה הוא מה שמאפשר ל-Excel להציג מספר אחרון-ידוע כשקובץ המקור במצב לא מקוון, וחוברת עבודה מחוללת נשלחת בלעדיו. ההשלכה נוחתת על הנמען, לא עליכם. פתחו קובץ כזה במקום שבו היעד אינו נגיש, מחשב נייד מנותק מה-VPN או שיתוף ששונה שמו, והנוסחאות התלויות בקישור יפתרו ל-#REF! או ייתקעו מאחורי הנחיית עדכון. אז שני כללים נובעים מכך. אל תבטיחו שחוברת עבודה מחוללת תציג את ערכיה המקושרים חיצונית במצב לא מקוון. וקראו ExternalLinks.Count שאינו אפס כתנאי-מקדים למסירה ולא כתכונה: כל יעד חייב להיות נגיש מהיכן שהקובץ ייפתח למעשה

מה קורא ה-XLS משמר בייט-אחר-בייט

עבור מבנים שאינו ממדל, לצד ה-BIFF יש תשובה שונה: השאירו אותם בדיוק כפי שנמצאו. מטמוני ציר ותצוגות ציר (משפחת רשומות SX*), הגדרות QueryTable, חיבורי נתונים חיצוניים, תצוגות מותאמות אישית, תמונות כותרת ורשומות ערכת נושא כולם עוברים מחזור פתיחה-ושמירה כבלוקי רשומות גולמיים, לא מנותחים ולא משונים. ההפניות החיצוניות עצמן עוברות סבב דרך רשומות ה-EXTERNSHEET וה-SupBook הבסיסיות. אין API מודפס ליצירתן בצד ה-XLS, אך קישור קיים שורד עריכה ללא פגיעה

שימור בייט-אחר-בייט הוא ערבות אמיתית עם קצה חד. מכיוון ששום דבר אינו קורא מבנה משומר, העריכות שלכם אינן יכולות להשחית אותו. מאותה סיבה, שום דבר גם אינו מעדכן אותו. הכניסו שורות דרך אזור שמטמון ציר משומר או טבלת שאילתה מצביעים אליו, והמבנה מחזיק את הקואורדינטות המקוריות שלו בעוד הנתונים שמתחתיו זזים. הקובץ עדיין XML או BIFF תקין; המשמעות סטתה בשקט מהיישור, ושום שגיאה אינה נורית לומר לכם זאת. הפריסה הניתנת להגנה היא לשמור עריכות מחוללות בגיליונות שאינם מחזיקים מבנים משומרים, וזו אותה משמעת המגנה על גיליונות נעולים ומוגדרי-הדפסה במאמר שלנו על הגנת גיליון עבודה והגדרת עמוד

אימות הקובץ שכתבתם בפועל

שני מצבי הכשל שקטים בזמן הכתיבה, ולכן הקביעה החשובה נעשית על-ידי פתיחה מחדש של הפלט ולא על-ידי הסתמכות על הקוד שהפיק אותו. שלוש בדיקות מכסות כמעט הכול. פתחו מחדש את הקובץ וודאו ש-HasVbaProject עדיין מחזירה true בכל פעם שמאקרו היה צפוי, מה שתופס מטען שהושמט וסיומת שגויה בבדיקה אחת. קראו את ExternalLinks.Count והשוו אותו מול הספירה שלפני הכתיבה מחדש. אחר כך פתחו את הקובץ פעם אחת ב-Excel עם מאקרו מושבת, מפני שאימות סוג התוכן של Excel מחמיר יותר מכל ספרייה, ו-Excel הוא התוכנית שלפיה הלקוחות שלכם ישפטו את הקובץ

שום דבר מכל זה אינו דורש ניתוח מלא בכניסה. כאשר חוברות עבודה מגיעות בנפח ואתם רק צריכים למיין אילו מהן נושאות תוכן מנוהל, הבדיקה הקלה במאמר שלנו על רשימת גיליונות ובדיקה קלת-משקל של חוברות עבודה מאפשרת לכם לנתב קבצים נושאי-מאקרו ומקושרים אל צינור מחמיר יותר לפני שהכתיבה מחדש הראשונה אי-פעם רצה

כמה שאלות עולות לעיתים קרובות מספיק כדי לענות עליהן ישירות. HotXLS לעולם אינה מריצה את המאקרו שהיא משמרת: אין מנוע ריצה של VBA בספרייה, רק המנגנון לאחסן, להעתיק, לחלץ ולהזריק את הפרויקט כנתונים. בשרת זו תכונת אבטחה שכדאי לציין, מכיוון שמאקרו עוין שעובר דרך הצינור נשאר אינרטי עד ש-Excel שולחני פותח את הקובץ ומשתמש מאפשר תוכן. המרת .xlsm ל-.xlsx תוך שמירת המאקרו אינה אפשרית, וזה כלל הפורמט ולא מגבלת ספרייה: סוג התוכן של .xlsx מצהיר על חוברת עבודה נטולת-מאקרו, ולכן התוצאות הכנות היחידות הן להישאר .xlsm או לקרוא ל-ClearVbaProject ולשלוח קובץ שבאמת אין בו אף אחד. שינוי השם השקט הוא הבחירה היחידה שאינה מספקת איש. וכאשר תאים מקושרים מציגים #REF! אחרי כתיבה מחדש, הסיבה היא מטמון הערכים החסר שנדון לעיל: הקובץ החדש נושא את היעד אך לא את המספרים שבמטמון, ולכן Excel חייב לפתור את המקור בזמן הפתיחה, ונתיב לא נגיש או תלוי-סביבה מביס אותו. או הבטיחו שהיעד נגיש, או כתבו ערכים מחושבים אל התאים לפני המסירה והשמיטו את התלות לחלוטין

עריכת חוברות העבודה של אנשים אחרים היא בעיקר העבודה של שימור דברים שלא כתבתם ואינכם מבינים במלואם. מתקני סבב העריכה של VBA וקישורים חיצוניים המתוארים כאן נשלחים עם HotXLS Component עבור Delphi ו-C++Builder, יחד עם מאפייני הביקורת המאפשרים לכם לזהות תוכן מנוהל ברגע שקובץ מגיע