مقال تقني

الترجمة المتقاطعة (Cross-Compiling) لمكونات Delphi لـ C++Builder: تجنب المشكلات الخارجية غير المحلولة (Unresolved Externals)

عند الحفاظ على مكون VCL مكتوب بلغة Delphi ولكن يستخدمه كل من مستخدمي Delphi و C++Builder، ستدرك سريعًا أن كلا نظامي البناء يتعاملان مع التبعيات بشكل مختلف تمامًا. الوحدة التي يتم تجميعها بشكل مثالي في Delphi قد تتسبب في أخطاء رابط (linker) كارثية في C++Builder. هذا التناقض هو فخ متكرر لمؤلفي المكونات، خاصة عند إضافة وحدات داخلية جديدة إلى مكتبة موجودة

إليك تفصيل دقيق لسبب حدوث ذلك، باستخدام أمثلة من العالم الحقيقي من مكون HotXLS، وكيفية حماية عملية الترجمة المتقاطعة الخاصة بك باستخدام التضمينات الصريحة (explicit includes)، و {$HPPEMIT}، وربط pragma

الفخ: التجميع الضمني مقابل التضمين الصريح

لنفترض أنك أنشأت وحدة Delphi جديدة، lxXlsSummary.pas، للتعامل مع البيانات الوصفية للمستند، وتستخدمها (uses) في وحدة التحليل الرئيسية الخاصة بك، lxRead.pas. تضغط على تجميع (compile) في بيئة التطوير المتكاملة Delphi، فيتحول البناء إلى اللون الأخضر، وتقوم بشحن التحديث

في اليوم التالي، يبلغ مستخدمو C++Builder عن خطأ أثناء مرحلة الربط (linking): 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 و 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.

التحقق من التكامل المستمر (Continuous Integration)

الدفاع النهائي ضد هذا الفخ هو التحقق من صحة CI/CD. لا تعتمد أبدًا على بناء Delphi الناجح وحده قبل شحن مكون ثنائي اللغة. يجب أن تستدعي نصوص البناء (build scripts) الخاصة بك MSBuild أو أدوات سطر الأوامر bcc32c في مشاريع C++Builder (مثل build-Win32-Lib-CB.cmd) وتشغيل رابط كامل (complete link) للإصدار التجريبي لـ C++ والعروض التوضيحية الكاملة. فقط عندما ينجح رابط C++، يمكنك التأكد من أن جميع وحدات Delphi مسجلة بشكل صحيح وتعرض رموزها لوقت تشغيل C++ (runtime)

ملاحظة: يتم الحفاظ على توافق المترجم عبر الأنظمة الأساسية (cross-platform) بصرامة عبر إصدارات Delphi و C++Builder من مكون HotXLS VCL