מאמר טכני

קימפול צולב (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)

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

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

unit lxXlsSummary;

interface

{$IFDEF WINDOWS}
  // Inject a pragma link into the generated C++ header file
  // This forces the C++ linker to include the corresponding .obj file
  {$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;
  // Metadata extraction logic here
end;

end.

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

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

כדי למנוע לחלוטין שבירה של בניית פרויקטי 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 VCL Component