Khi duy trì một thành phần VCL được viết bằng Delphi nhưng được sử dụng bởi cả người dùng Delphi và C++Builder, bạn sẽ nhanh chóng nhận ra rằng hai hệ thống xây dựng xử lý các phụ thuộc rất khác nhau. Một đơn vị biên dịch hoàn hảo trong Delphi có thể gây ra lỗi trình liên kết (linker errors) thảm khốc trong C++Builder. Sự khác biệt này là một cạm bẫy thường gặp đối với các tác giả thành phần, đặc biệt là khi thêm các đơn vị nội bộ mới vào một thư viện hiện có
Dưới đây là bảng phân tích chi tiết về lý do tại sao điều này xảy ra (sử dụng các ví dụ thực tế từ thành phần HotXLS) và cách bảo đảm an toàn cho quá trình biên dịch chéo của bạn bằng cách sử dụng tính năng đưa vào rõ ràng (explicit includes), {$HPPEMIT} và liên kết pragma
Cạm bẫy: Biên dịch ngầm định so với Đưa vào rõ ràng
Giả sử bạn tạo một đơn vị Delphi mới, lxXlsSummary.pas, để xử lý siêu dữ liệu tài liệu và bạn sử dụng (uses) nó trong đơn vị phân tích cú pháp chính của mình, lxRead.pas. Bạn nhấn biên dịch trong IDE Delphi, bản dựng chuyển sang màu xanh lục và bạn phát hành bản cập nhật
Ngày hôm sau, những người dùng C++Builder của bạn báo cáo một lỗi trong giai đoạn liên kết: Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj
Cách tiếp cận của Delphi (dcc32)
Khi trình biên dịch Delphi xử lý một gói (.dpk), nó xem xét các đơn vị được liệt kê rõ ràng trong mệnh đề contains. Nếu một trong các đơn vị đó sử dụng (uses) một đơn vị bên ngoài (như lxXlsSummary.pas) không có trong danh sách contains, trình biên dịch Delphi sẽ thực hiện liên kết tĩnh ngầm định. Nó chỉ cần tìm tệp .pas trong đường dẫn tìm kiếm, biên dịch nó thành một .dcu, và nhúng nó vào kết quả .bpl. Việc xây dựng thành công, hoàn toàn che giấu sự thiếu sót đó
Cách tiếp cận của C++Builder (MSBuild / .cbproj)
Hệ thống xây dựng của C++Builder chặt chẽ hơn nhiều. Nó chỉ tạo các tệp đối tượng C++ (.obj) và tệp tiêu đề (.hpp) cho các đơn vị Delphi được liệt kê rõ ràng trong nhóm mục <DelphiCompile> của tệp .cbproj. Bởi vì lxXlsSummary.pas chưa bao giờ được đăng ký rõ ràng trong tệp dự án, không có lxXlsSummary.obj nào được tạo. Khi trình liên kết cố gắng giải quyết các lệnh gọi được thực hiện bởi lxRead.obj, các biểu tượng bị thiếu, dẫn đến lỗi tham chiếu bên ngoài chưa được giải quyết (unresolved external)
Giải quyết tham chiếu bên ngoài bằng Pragma Link và HPPEMIT
Nếu bạn muốn đảm bảo rằng một đơn vị được liên kết chính xác trong C++ mà không buộc người dùng phải thêm tệp .obj vào dự án của họ theo cách thủ công, bạn có thể sử dụng chỉ thị {$HPPEMIT} của Delphi. Điều này cho trình biên dịch Delphi biết để chèn một chỉ thị C++ #pragma link cụ thể vào tệp .hpp được tạo
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.
Khi C++Builder bao gồm lxXlsSummary.hpp, trình biên dịch bắt gặp #pragma link và tự động yêu cầu trình liên kết (ILINK32/ILINK64) giải quyết các biểu tượng từ lxXlsSummary.obj
Quy tắc vàng cho việc bảo trì thành phần
Để hoàn toàn tránh việc phá vỡ các bản dựng C++Builder, bạn phải áp dụng một chính sách đăng ký nghiêm ngặt. Bất cứ khi nào một đơn vị Pascal mới được thêm vào thư viện của bạn, nó phải được đăng ký rõ ràng trên cả ba loại tệp dự án cùng một lúc
1. Cập nhật Dự án C++Builder (.cbproj / .bpk)
Mở tệp .cbproj trong trình soạn thảo văn bản và thêm đơn vị mới vào danh sách biên dịch, đảm bảo bạn cung cấp một thứ tự bản dựng duy nhất. Nếu sử dụng các phiên bản C++Builder cũ hơn với tệp .bpk, hãy đảm bảo thẻ <file containerid="PascalCompiler" designclass="" filename="lxXlsSummary.pas" formname="" localcommand="" unitname="lxXlsSummary"></file> được thêm vào
<DelphiCompile Include="lxXlsSummary.pas">
<BuildOrder>101</BuildOrder>
</DelphiCompile>
2. Cập nhật Gói Delphi (.dpk)
Thêm đơn vị vào mệnh đề contains một cách rõ ràng. Điều này đảm bảo trình biên dịch Delphi không phải dựa vào liên kết ngầm định, một điều thường được coi là thực tiễn tồi
package HotXLS;
{$R *.res}
{$ALIGN 8}
{$ASSERTIONS ON}
{$BOOLEVAL OFF}
requires
rtl,
vcl;
contains
lxRead in 'lxRead.pas',
lxXlsSummary in 'lxXlsSummary.pas';
end.
Xác thực tích hợp liên tục (CI)
Sự bảo vệ cuối cùng chống lại cạm bẫy này là xác thực CI/CD. Không bao giờ chỉ dựa vào một bản dựng Delphi thành công trước khi phát hành một thành phần đa ngôn ngữ. Các tập lệnh bản dựng của bạn phải gọi MSBuild hoặc các công cụ dòng lệnh bcc32c trên các dự án C++Builder (ví dụ: build-Win32-Lib-CB.cmd) và chạy liên kết hoàn chỉnh cho các bản dùng thử và demo C++. Chỉ khi trình liên kết C++ thành công, bạn mới có thể chắc chắn rằng tất cả các đơn vị Delphi đã được đăng ký chính xác và bộc lộ các biểu tượng của chúng cho runtime C++
Lưu ý: Tính tương thích của trình biên dịch chéo nền tảng được duy trì nghiêm ngặt trên các phiên bản Delphi và C++Builder của HotXLS VCL Component