เมื่อต้องบำรุงรักษาคอมโพเนนต์ VCL ที่เขียนใน Delphi แต่ใช้งานโดยผู้ใช้ทั้ง Delphi และ C++Builder คุณจะตระหนักได้อย่างรวดเร็วว่าระบบบิลด์ทั้งสองจัดการกับการพึ่งพา (dependencies) แตกต่างกันมาก ยูนิตที่คอมไพล์ได้อย่างสมบูรณ์ใน Delphi อาจทำให้เกิดข้อผิดพลาดของลิงเกอร์ที่รุนแรงใน C++Builder ความคลาดเคลื่อนนี้เป็นกับดักที่พบบ่อยสำหรับผู้เขียนคอมโพเนนต์ โดยเฉพาะอย่างยิ่งเมื่อเพิ่มยูนิตภายในใหม่เข้าไปในไลบรารีที่มีอยู่
นี่คือรายละเอียดโดยละเอียดว่าทำไมสิ่งนี้ถึงเกิดขึ้น โดยใช้ตัวอย่างในโลกแห่งความเป็นจริงจากคอมโพเนนต์ HotXLS และวิธีป้องกันกระบวนการคอมไพล์ข้ามแพลตฟอร์มของคุณโดยใช้ explicit includes, {$HPPEMIT} และ pragma linking
กับดัก: Implicit Compilation เทียบกับ Explicit Inclusion
สมมติว่าคุณสร้างยูนิต Delphi ใหม่ lxXlsSummary.pas เพื่อจัดการข้อมูลเมตาของเอกสาร และคุณใช้งานมันในยูนิตการแยกวิเคราะห์หลักของคุณ lxRead.pas คุณกดคอมไพล์ใน Delphi IDE การสร้างสำเร็จ และคุณจัดส่งการอัปเดต
วันรุ่งขึ้น ผู้ใช้ C++Builder ของคุณรายงานข้อผิดพลาดระหว่างขั้นตอนการเชื่อมโยง: Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj
วิธีของ Delphi (dcc32)
เมื่อคอมไพเลอร์ Delphi ประมวลผลแพ็คเกจ (.dpk) มันจะดูที่ยูนิตที่ระบุไว้อย่างชัดเจนในประโยค contains หากหนึ่งในยูนิตเหล่านั้นใช้งานยูนิตภายนอก (เช่น 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
การแก้ปัญหา Externals ด้วย Pragma Link และ HPPEMIT
หากคุณต้องการให้แน่ใจว่ายูนิตเชื่อมโยงอย่างถูกต้องใน C++ โดยไม่ต้องบังคับให้ผู้ใช้เพิ่มไฟล์ .obj ด้วยตนเองในโครงการ คุณสามารถใช้ไดเรกทิฟ {$HPPEMIT} ของ Delphi สิ่งนี้บอกให้คอมไพเลอร์ Delphi แทรกไดเรกทิฟ C++ #pragma link เฉพาะลงในไฟล์ .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 ในเท็กซ์เอดิเตอร์และเพิ่มยูนิตใหม่ในรายการคอมไพล์ ตรวจสอบให้แน่ใจว่าคุณระบุลำดับการสร้างที่ไม่ซ้ำกัน หากใช้ 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 ที่สำเร็จเพียงอย่างเดียวก่อนที่จะจัดส่งคอมโพเนนต์สองภาษา สคริปต์บิลด์ของคุณต้องเรียกใช้ MSBuild หรือเครื่องมือบรรทัดคำสั่ง bcc32c บนโครงการ C++Builder (เช่น build-Win32-Lib-CB.cmd) และเรียกใช้งานลิงก์ที่สมบูรณ์ของเวอร์ชันทดลองและเดโมฉบับเต็มของ C++ ต่อเมื่อลิงเกอร์ C++ ทำงานสำเร็จเท่านั้น คุณจึงจะแน่ใจได้ว่ายูนิต Delphi ทั้งหมดได้รับการลงทะเบียนอย่างถูกต้องและเปิดเผยสัญลักษณ์ของพวกมันไปยังรันไทม์ C++
หมายเหตุ: ความเข้ากันได้ของคอมไพเลอร์ข้ามแพลตฟอร์มได้รับการบำรุงรักษาอย่างเคร่งครัดในรุ่น Delphi และ C++Builder ของ HotXLS VCL Component
การตรวจสอบ CI ควรสร้างทั้งแพ็กเกจ Delphi และโปรเจ็กต์ C++Builder ในการรันเดียวกัน แล้วตรวจว่าไฟล์อ็อบเจ็กต์ทุกตัวถูกส่งเข้า linker จริง การคอมไพล์ผ่านเฉพาะฝั่ง Delphi ยังไม่ยืนยันว่าการส่งออกสัญลักษณ์สำหรับ C++Builder ครบถ้วน