מאמר טכני

קימפול צולב (Cross-Compiling) של רכיבי Delphi עבור C++Builder: הימנעות מהפניות חיצוניות בלתי פתורות

כאשר מתחזקים רכיב VCL שנכתב ב-Delphi אך משמש הן משתמשי Delphi והן משתמשי C++Builder, מבינים במהירות ששתי מערכות הבנייה מתייחסות לתלויות באופן שונה מאוד. יחידה שמתקמפלת בצורה מושלמת ב-Delphi עלולה לגרום לשגיאות מקשר (linker) קטסטרופליות ב-C++Builder. פער זה מהווה מלכודת נפוצה למחברי רכיבים, במיוחד בעת הוספת יחידות פנימיות חדשות לספרייה קיימת

להלן פירוט מדוע זה קורה - באמצעות דוגמאות מהעולם האמיתי מתוך רכיב HotXLS - וכיצד לחסן את תהליך הקימפול הצולב שלכם באמצעות הכללות מפורשות (explicit includes), {$HPPEMIT}, וקישור pragma

המלכודת: קימפול מרומז (Implicit) מול הכללה מפורשת (Explicit)

נניח שאתם יוצרים יחידת Delphi חדשה, lxXlsSummary.pas, כדי לטפל במטא-נתונים של מסמכים, ואתם מוסיפים אותה ל-uses ביחידת הניתוח הראשית שלכם, lxRead.pas. אתם לוחצים על קימפול ב-IDE של Delphi, בניית הפרויקט עוברת בהצלחה (ירוק), ואתם משחררים את העדכון

למחרת, משתמשי ה-C++Builder שלכם מדווחים על שגיאה במהלך שלב הקישור: Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj

הדרך של Delphi (dcc32)

כאשר המהדר של Delphi מעבד חבילה (.dpk), הוא מסתכל על היחידות הרשומות במפורש בפסוקית ה-contains. אם אחת מאותן יחידות מבצעת uses ליחידה חיצונית (כמו lxXlsSummary.pas) שאינה ברשימת ה-contains, המהדר של Delphi מבצע קישור סטטי מרומז (implicit static linking). הוא פשוט מוצא את קובץ ה-.pas בנתיב החיפוש, מקמפל אותו ל-.dcu, ואופה אותו לתוך ה-.bpl שנוצר. הבנייה מצליחה, ומסווה לחלוטין את ההשמטה

הדרך של C++Builder (MSBuild / .cbproj)

מערכת הבנייה של C++Builder מחמירה הרבה יותר. היא מייצרת קבצי אובייקטים של C++ (.obj) וכותרות (.hpp) רק עבור יחידות ה-Delphi הרשומות במפורש בקבוצת הפריטים <DelphiCompile> בקובץ .cbproj. מכיוון ש-lxXlsSummary.pas מעולם לא נרשמה במפורש בקובץ הפרויקט, לא נוצר lxXlsSummary.obj. כאשר המקשר מנסה לפתור את הקריאות שבוצעו על ידי lxRead.obj, הסמלים חסרים, מה שמוביל לשגיאת הפניה חיצונית בלתי פתורה (unresolved external)

השוואת צינור בנייה עבור רכיב ה-HotXLS ל-Delphi: dcc32 של Delphi מהדר בעקיפין יחידה שאינה רשומה אל תוך החבילה והבנייה עוברת בירוק, בזמן ש-MSBuild של C++Builder לעולם אינו מייצר את קובץ ה-object שלה ו-ILINK32 נכשל עם unresolved external
אותה יחידה שאינה מפורטת לוקחת שני נתיבים שונים: dcc32 מסתיר את ההשמטה דרך קישור סטטי מרומז בעוד ה-linker המחמיר יותר של C++Builder חושף אותה

פתרון הפניות חיצוניות עם Pragma Link ו-HPPEMIT

אם אתם רוצים להבטיח שיחידה מקושרת כראוי ב-C++ מבלי להכריח את המשתמש להוסיף ידנית את קובץ ה-.obj לפרויקט שלו, תוכלו להשתמש בהוראת ה-{$HPPEMIT} של Delphi. הוראה זו אומרת למהדר של Delphi להזריק הוראת #pragma link ספציפית של C++ לתוך קובץ ה-.hpp שנוצר

unit lxXlsSummary;

interface

{$IFDEF WINDOWS}
  // הזרק pragma link לתוך קובץ הכותרת (header) של C++ שנוצר
  // זה מכריח את המקשר של C++ לכלול את קובץ ה-.obj המתאים
  {$HPPEMIT '#pragma link "lxXlsSummary.obj"'}
{$ENDIF}

uses
  SysUtils, Classes;

type
  TXlsSummaryInfo = class(TObject)
  public
    Title: string;
    Author: string;
    CreateTime: TDateTime;
  end;

function XlsReadSummaryInformation(const FileName: string): TXlsSummaryInfo;

implementation

function XlsReadSummaryInformation(const FileName: string): TXlsSummaryInfo;
begin
  Result := TXlsSummaryInfo.Create;
  // לוגיקת חילוץ מטא-נתונים כאן
end;

end.

כאשר C++Builder כולל את lxXlsSummary.hpp, המהדר נתקל ב-#pragma link ומורה אוטומטית למקשר (ILINK32/ILINK64) לפתור סמלים מתוך lxXlsSummary.obj

זרימה בת ארבע עמודות של מנגנון קישור הפרגמה HPPEMIT ב-Delphi: הנחיה מוצהרת גורמת ל-dcc32 להזריק את סדר הקישור אל lxXlsSummary.hpp, C++Builder צורך אותו ב-include, ו-ILINK32 פותר את XlsReadSummaryInformation באופן אוטומטי
סדר הקישור נכתב פעם אחת בקוד מקור Pascal, נוסע בתוך ה-header שנוצר, ומגיע לכל בניית C++ עתידית ללא עריכות פרויקט ידניות

כלל הזהב לתחזוקת רכיבים

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

1. עדכון פרויקט ה-C++Builder (.cbproj / .bpk)

פתחו את קובץ ה-.cbproj בעורך טקסט והוסיפו את היחידה החדשה לרשימת הקימפול, תוך הקפדה לספק סדר בנייה (build order) ייחודי. אם אתם משתמשים בגרסאות ישנות של C++Builder עם קבצי .bpk, ודאו שתגית ה-<file containerid="PascalCompiler" designclass="" filename="lxXlsSummary.pas" formname="" localcommand="" unitname="lxXlsSummary"></file> מתווספת

<DelphiCompile Include="lxXlsSummary.pas">
  <BuildOrder>101</BuildOrder>
</DelphiCompile>

2. עדכון חבילת ה-Delphi (.dpk)

הוסיפו את היחידה לפסוקית ה-contains המפורשת. זה מבטיח שהמהדר של Delphi לא יצטרך להסתמך על קישור מרומז, שנחשב בדרך כלל לפרקטיקה רעה בכל מקרה

package HotXLS;

{$R *.res}
{$ALIGN 8}
{$ASSERTIONS ON}
{$BOOLEVAL OFF}

requires
  rtl,
  vcl;

contains
  lxRead in 'lxRead.pas',
  lxXlsSummary in 'lxXlsSummary.pas';

end.

אימות באמצעות אינטגרציה רציפה (CI)

ההגנה האולטימטיבית מפני מלכודת זו היא אימות CI/CD. לעולם אל תסתמכו אך ורק על בניית Delphi מוצלחת לפני הפצת רכיב דו-לשוני. סקריפטי הבנייה שלכם חייבים להפעיל את MSBuild או את כלי שורת הפקודה bcc32c על פרויקטי ה-C++Builder (למשל, build-Win32-Lib-CB.cmd) ולהריץ קישור שלם של הדגמות הניסיון (trial) וההדגמות המלאות ב-C++. רק כאשר מקשר ה-C++ מצליח, תוכלו להיות בטוחים שכל יחידות ה-Delphi רשומות כראוי וחושפות את הסמלים שלהן לזמן הריצה של C++

הערה: תאימות מהדר חוצה פלטפורמות נשמרת בקפידה במהדורות Delphi ו-C++Builder של רכיב HotXLS Delphi VCL Component