مقاله فنی

HotXLS: حفظ ماکروهای VBA و پیوندهای خارجی در دلفی

کاری را در نظر بگیرید که تقریباً هیچ نمی‌کند: یک کارپوشه ماهانه را باز می‌کند، تاریخ امروز را در یک سلول می‌نویسد، و آن را ذخیره می‌کند. آن را به اندازه کافی از یک سرویس عبور دهید و به هر حال شکایتی می‌رسد. ماکروها رفته‌اند، یا نرخ‌های ارز پیوندشده حالا #REF! نشان می‌دهند، و تیم عملیات مطمئن است که کد شما آن‌ها را پاک کرده است. هیچ‌چیز را پاک نکرده است. آنچه معمولاً رخ داده این است که یک کارپوشه ماکرودار با یک نام ساده .xlsx بیرون رفته، و اکسل از قواعد نوع محتوای ECMA-376 پیروی کرده است: بسته‌ای که نوع محتوایش هیچ VBA اعلام نمی‌کند نمی‌تواند یک پروژه VBA بارگذاری کند، صرف نظر از اینکه بایت‌ها همان‌جا نشسته باشند یا نه. فایل خراب نشده است. به حالتی تغییر نام داده شده که اکسل ملزم است بخشی از آن را نادیده بگیرد

ماکروها و پیوندهای کارپوشه خارجی دو چیزی هستند که اتوماسیون قابل‌اعتمادترین از دستشان می‌دهد، به همان دلیل زیربنایی. هر دو بیرون از شبکه سلولی زندگی می‌کنند که کد ویرایش واقعاً به آن دست می‌زند، پس کدی که بر حسب ردیف‌ها و ستون‌ها استدلال می‌کند بدون آنکه هرگز دستور حذفی صادر کند آن‌ها را می‌اندازد. HotXLS یک کتابخانه بومی دلفی و C++Builder است که XLS و XLSX را بدون نصب اکسل می‌خواند و می‌نویسد، و با هر دو دارایی به‌عنوان محموله‌هایی رفتار می‌کند که عمداً حمل می‌کند نه داده‌ای که اتفاقی کپی می‌کند. آنچه در ادامه می‌آید این است که هرکدام از مسیر ذخیره شما چه نیاز دارد، و تضمین‌ها کجا متوقف می‌شوند

چرا این دو دارایی زیر بازنویسی متفاوت رفتار می‌کنند

یک پروژه VBA یک باینری مات واحد است. در یک بسته OOXML همان فایل vbaProject.bin است؛ در یک فایل قدیمی BIFF یک انباره OLE است. دقیقاً دو راه برای از دست دادنش وجود دارد: نویسنده هرگز آن را در خروجی کپی نمی‌کند، یا خروجی نوع فایلی می‌گیرد که آن را ممنوع می‌کند. هر دو شکست کامل و بی‌صدا هستند. پروژه یا هست یا نیست

یک پیوند خارجی اصلاً یک بلاب نیست. یک گراف کوچک از روابط است: یک مسیر یا URL هدف که به کارپوشه دیگری اشاره می‌کند، فهرست نام کاربرگ‌هایی که آن هدف افشا می‌کند، و یک کش اختیاری از مقادیری که آخرین بار در آن کاربرگ‌ها دیده شده تا اکسل بتواند وقتی هدف آفلاین است چیزی نشان دهد. آن سه بخش زیر یک بازنویسی طول عمرهای متفاوتی دارند، و یک کتابخانه می‌تواند برخی را وفادارانه حفظ کند و برخی دیگر را بی‌سروصدا بیندازد. این عدم تقارن همان بخشی است که ارزش دقیق‌شدن دارد، چون هیچ‌چیز در کد ویرایش سلول آن را آشکار نخواهد کرد

دیاگرام مقایسه یک بلاب پروژه VBA در برابر سه بخش یک پیوند کارپوشه خارجی که HotXLS در یک بازنویسی دلفی حمل می‌کند
یک پروژه VBA یک بازنویسی را به‌صورت محموله باینری همه‌یا‌هیچ پشت سر می‌گذارد، در حالی که یک پیوند خارجی گراف کوچکی است که هدف، نام کاربرگ‌ها و مقادیر کش‌شده‌اش می‌توانند مستقل از هم حفظ یا انداخته شوند

حمل یک پروژه VBA در یک بازنویسی XLSX

در سمت XLSX، TXLSXWorkbook محموله ماکرو را عیناً نگه می‌دارد. ویژگی VbaProject بایت‌های خام vbaProject.bin را درون یک AnsiString نگه می‌دارد، و یک رشته خالی شیوه‌ای است که مدل می‌گوید ماکرویی وجود ندارد. دور آن سه عملیات نشسته‌اند: HasVbaProject پاسخ می‌دهد که آیا پروژه‌ای حاضر است، ClearVbaProject آن را عمداً حذف می‌کند، و LoadVbaProjectFromFile پروژه‌ای را که از یک قالب استخراج شده تزریق می‌کند. آن فراخوانی آخر بیش از آنچه به نظر می‌رسد ارزش دارد. به کارپوشه‌های تولیدشده اجازه می‌دهد بدون کشیدن یک فایل قالب کامل در خط لوله، یک پروژه ماکروی استاندارد بردارند

دیاگرام جریان یک فراخوانی ذخیره در دلفی که در آن پسوند .xlsm نوع محتوای ماکرودار را انتخاب می‌کند و .xlsx باعث می‌شود اکسل بی‌سروصدا ماکروها را رد کند
HotXLS بایت‌های خام vbaProject.bin را در طول ذخیره حمل می‌کند، و پسوند .xlsm همان چیزی است که نوع محتوای ماکرودار مورد نیاز اکسل را انتخاب می‌کند
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');

    // پسوند .xlsm تزیینی نیست: نوع محتوای
    // ماکرودار را درون بسته انتخاب می‌کند
    Book.SaveAs('monthly-report.xlsm');
  finally
    Book.Free;
  end;
end;

خط ذخیره جایی است که کل مسئله می‌چرخد. کارپوشه‌ای که یک پروژه VBA نگه می‌دارد باید با معنای ماکرودار نوشته شود، و HotXLS وقتی نام هدف به .xlsm ختم می‌شود آن را اعمال می‌کند. به‌جای آن .xlsx به آن بدهید و اکسل ماکروها را رد می‌کند، هرچند بایت‌ها به‌طور فیزیکی در بسته حاضرند و به‌خوبی از حالت سریال خارج می‌شدند. پسوند تزیین نیست؛ نوع محتوایی را انتخاب می‌کند که به اکسل می‌گوید یک پروژه VBA مجاز به وجود داشتن است. بیشتر وقت‌ها فقط باید محموله را حمل کنید. وقتی باید درون آن را بخوانید، مثلاً برای فهرست‌کردن نام ماژول‌ها در یک گزارش ممیزی، ParsedVBAProject یک مدل ماژول تجزیه‌شده افشا می‌کند در حالی که VbaProject همان بایت‌های اصلی دست‌نخورده می‌ماند

استفاده مجدد از ماکروهای کارپوشه‌های XLS قدیمی

نمای BIFF همان مجموعه ابزار را با یک گام اضافه بازتاب می‌دهد. HasVBAProject یک فایل بارگذاری‌شده را کاوش می‌کند، SaveVBAProjectToFile انباره پروژه را روی دیسک می‌نویسد، و LoadVBAProjectFromFile یکی را به کارپوشه دیگری بازمی‌خواند. این میان‌بر از طریق یک فایل، یک کار رایج مدرن‌سازی را ساده می‌کند: ماکروها را از یک مدل دوران ۲۰۰۳ بیرون بکشید و در خروجی XLS تازه‌تولیدشده بکارید، بدون نیاز به قالب اصلی در زمان اجرا

var
  Src, Dst: IXLSWorkbook;   // ارجاع‌های اینترفیسی: نیازی به 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 نگه داشته می‌شود، پس هرگز آن را دستی آزاد نمی‌کنید؛ TXLSXWorkbook در سمت XLSX یک شیء ساده است که باید در try..finally بپیچید و آزاد کنید. دو قرارداد را در یک unit مخلوط کنید و کرش‌های آزادسازی دوباره در پی می‌آیند. یک مرز دیگر که ارزش رعایت دارد: استخراج و تزریق را درون یک قالب فایل واحد نگه دارید. انباره پروژه 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 پیوند نگه می‌دارد تجزیه یا بازنویسی نمی‌کند. آن کش همان چیزی است که به اکسل اجازه می‌دهد وقتی فایل منبع آفلاین است آخرین عدد شناخته‌شده را نشان دهد، و یک کارپوشه تولیدشده بدون آن عرضه می‌شود. پیامد روی گیرنده فرود می‌آید، نه روی شما. چنین فایلی را جایی باز کنید که هدف دست‌نیافتنی است، یک لپ‌تاپ خارج از VPN یا یک اشتراک که تغییر نام داده، و فرمول‌هایی که به پیوند وابسته‌اند به #REF! حل می‌شوند یا پشت یک اعلان به‌روزرسانی گیر می‌کنند. پس دو قاعده از این بیرون می‌آید. قول ندهید که یک کارپوشه تولیدشده مقادیر پیوندشده خارجی‌اش را آفلاین نمایش می‌دهد. و یک ExternalLinks.Count ناصفر را به‌عنوان یک پیش‌شرط تحویل بخوانید نه یک ویژگی: هر هدف باید از هرجایی که فایل واقعاً باز خواهد شد قابل دسترس باشد

دیاگرام اینکه کدام بخش‌های یک پیوند کارپوشه خارجی HotXLS یک بازنویسی را پشت سر می‌گذارند و وقتی مقادیر کش‌شده پشت sheetDataSet آفلاین غایب‌اند چه رخ می‌دهد
HotXLS هدف پیوند و نام کاربرگ‌هایش را رفت‌وبرگشت می‌دهد، اما مقادیر کش‌شده سلول‌ها پشت sheetDataSet به فایل تولیدشده حمل نمی‌شوند

خواننده XLS چه چیزی را بایت‌به‌بایت حفظ می‌کند

برای ساختارهایی که مدل نمی‌کند، سمت BIFF پاسخ متفاوتی دارد: آن‌ها را دقیقاً همان‌طور که یافته رها می‌کند. کش‌های محوری و نماهای محوری (خانواده رکورد SX*)، تعریف‌های QueryTable، اتصال‌های داده خارجی، نماهای سفارشی، تصاویر سرصفحه و رکوردهای تم همگی یک چرخه باز-و-ذخیره را به‌صورت بلوک‌های رکورد خام، تجزیه‌نشده و تغییرنیافته، عبور می‌کنند. خود ارجاع‌های خارجی از طریق رکوردهای زیربنایی EXTERNSHEET و SupBook رفت‌وبرگشت می‌کنند. در سمت XLS هیچ API ساخت تایپ‌شده‌ای برای آن‌ها وجود ندارد، اما یک پیوند موجود ویرایش را دست‌نخورده پشت سر می‌گذارد

حفظ بایت‌به‌بایت یک تضمین واقعی با یک لبه تیز است. چون هیچ‌چیز یک ساختار حفظ‌شده را نمی‌خواند، ویرایش‌های شما نمی‌توانند آن را خراب کنند. به همان دلیل، هیچ‌چیز آن را به‌روز هم نمی‌کند. ردیف‌هایی را در ناحیه‌ای درج کنید که یک کش محوری یا جدول پرس‌وجوی حفظ‌شده به آن اشاره می‌کند، و ساختار مختصات اصلی‌اش را نگه می‌دارد در حالی که داده زیرش جابه‌جا می‌شود. فایل همچنان XML یا BIFF معتبر است؛ معنا بی‌سروصدا از هم‌ترازی خارج شده، و هیچ خطایی برای گفتن این به شما شلیک نمی‌شود. چیدمان قابل دفاع این است که ویرایش‌های تولیدشده را روی کاربرگ‌هایی نگه دارید که هیچ ساختار حفظ‌شده‌ای ندارند، که همان انضباطی است که کاربرگ‌های قفل‌شده و پیکربندی‌شده برای چاپ را در مقاله ما درباره محافظت کاربرگ و تنظیم صفحه محافظت می‌کند

تأیید فایلی که واقعاً نوشتید

هر دو حالت شکست در زمان نوشتن بی‌صدا هستند، پس ادعایی که اهمیت دارد با بازکردن دوباره خروجی ساخته می‌شود نه با اعتماد به کدی که آن را تولید کرده است. سه بررسی تقریباً همه‌چیز را پوشش می‌دهند. فایل را دوباره باز کنید و تأیید کنید که HasVbaProject هر وقت ماکرو انتظار می‌رفت همچنان true برمی‌گرداند، که یک محموله انداخته‌شده و یک پسوند اشتباه را در یک آزمون واحد می‌گیرد. ExternalLinks.Count را بخوانید و با شمار پیش از بازنویسی مقایسه کنید. سپس فایل را یک بار در اکسل با ماکروهای غیرفعال باز کنید، چون اعتبارسنجی نوع محتوای اکسل سخت‌گیرتر از هر کتابخانه‌ای است، و اکسل برنامه‌ای است که مشتریان شما فایل را با آن قضاوت خواهند کرد

هیچ‌کدام از این‌ها به یک تجزیه کامل در مسیر ورود نیاز ندارد. وقتی کارپوشه‌ها انبوه می‌رسند و فقط باید تشخیص دهید کدام‌ها محتوای تحت حاکمیت حمل می‌کنند، کاوش سبک در مقاله ما درباره فهرست‌کردن کاربرگ‌ها و بازرسی سبک کارپوشه به شما اجازه می‌دهد فایل‌های ماکرودار و پیونددار را پیش از آنکه نخستین بازنویسی اصلاً اجرا شود به یک خط لوله سخت‌گیرتر هدایت کنید

چند پرسش آن‌قدر زیاد پیش می‌آیند که ارزش پاسخ مستقیم دارند. HotXLS هرگز ماکروهایی را که حفظ می‌کند اجرا نمی‌کند: هیچ زمان اجرای VBA در کتابخانه وجود ندارد، فقط سازوکار ذخیره، کپی، استخراج و تزریق پروژه به‌صورت داده. روی یک سرور این یک ویژگی امنیتی است که ارزش بیان دارد، چون یک ماکروی خصمانه که از خط لوله عبور می‌کند تا وقتی یک اکسل دسکتاپ فایل را باز کند و یک کاربر محتوا را فعال کند بی‌اثر می‌ماند. تبدیل یک .xlsm به .xlsx و نگه‌داشتن ماکروها ممکن نیست، و این قاعده قالب است نه محدودیت کتابخانه: نوع محتوای .xlsx یک کارپوشه بدون ماکرو اعلام می‌کند، پس تنها نتایج صادقانه ماندن روی .xlsm یا فراخوانی ClearVbaProject و عرضه فایلی است که واقعاً هیچ ماکرویی ندارد. تغییر نام بی‌صدا تنها انتخابی است که هیچ‌کس را راضی نمی‌کند. و وقتی سلول‌های پیوندشده پس از یک بازنویسی #REF! نشان می‌دهند، علت همان کش مقدار گمشده است که در بالا بحث شد: فایل جدید هدف را حمل می‌کند ولی اعداد کش‌شده را نه، پس اکسل باید منبع را در زمان بازکردن حل کند، و یک مسیر دست‌نیافتنی یا نسبی به محیط آن را شکست می‌دهد. یا تضمین کنید که هدف قابل دسترس است یا مقادیر محاسبه‌شده را پیش از تحویل در سلول‌ها بنویسید و وابستگی را به کلی حذف کنید

ویرایش کارپوشه‌های دیگران عمدتاً کار حفظ چیزهایی است که شما ننوشته‌اید و کاملاً نمی‌فهمید. امکانات رفت‌وبرگشت VBA و پیوند خارجی که اینجا توصیف شدند همراه با HotXLS Delphi Component برای دلفی و C++Builder عرضه می‌شوند، به همراه ویژگی‌های ممیزی که به شما اجازه می‌دهند محتوای تحت حاکمیت را همان لحظه‌ای که فایلی می‌رسد شناسایی کنید