Utrzymując komponent VCL napisany w Delphi, ale wykorzystywany zarówno przez użytkowników Delphi, jak i C++Builder, szybko dostrzegasz, że oba systemy budowania traktują zależności bardzo różnie. Moduł, który kompiluje się bez zarzutu w Delphi, może wywołać katastrofalne błędy linkera w C++Builder. Ta rozbieżność jest częstą pułapką dla autorów komponentów, szczególnie przy dodawaniu nowych modułów wewnętrznych do istniejącej biblioteki
Oto szczegółowa analiza tego, dlaczego tak się dzieje, na rzeczywistych przykładach z komponentu HotXLS, oraz jak uczynić proces kompilacji skrośnej kuloodpornym za pomocą jawnych włączeń, dyrektywy {$HPPEMIT} i linkowania pragmą
Pułapka: kompilacja niejawna kontra jawne włączenie
Załóżmy, że tworzysz nowy moduł Delphi, lxXlsSummary.pas, do obsługi metadanych dokumentu, i korzystasz z niego przez uses w swoim głównym module parsującym, lxRead.pas. Naciskasz kompilację w IDE Delphi, budowanie kończy się na zielono, i wysyłasz aktualizację
Następnego dnia twoi użytkownicy C++Builder zgłaszają błąd w fazie linkowania: Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj
Sposób Delphi (dcc32)
Gdy kompilator Delphi przetwarza pakiet (.dpk), patrzy na moduły jawnie wymienione w klauzuli contains. Jeśli jeden z tych modułów korzysta przez uses z zewnętrznego modułu (jak lxXlsSummary.pas), którego nie ma na liście contains, kompilator Delphi wykonuje niejawne linkowanie statyczne. Po prostu znajduje plik .pas w ścieżce wyszukiwania, kompiluje go do .dcu i wtapia go w wynikowy .bpl. Budowanie kończy się sukcesem, całkowicie maskując pominięcie
Sposób C++Builder (MSBuild / .cbproj)
System budowania C++Builder jest znacznie bardziej rygorystyczny. Generuje pliki obiektowe C++ (.obj) i nagłówki (.hpp) tylko dla modułów Delphi jawnie wymienionych w grupie elementów <DelphiCompile> pliku .cbproj. Ponieważ lxXlsSummary.pas nigdy nie został jawnie zarejestrowany w pliku projektu, żaden lxXlsSummary.obj nie jest tworzony. Gdy linker próbuje rozwiązać wywołania wykonywane przez lxRead.obj, symbole są brakujące, co prowadzi do błędu nierozwiązanego symbolu zewnętrznego
Rozwiązywanie symboli zewnętrznych za pomocą pragma link i HPPEMIT
Jeśli chcesz zapewnić, że moduł jest poprawnie zlinkowany w C++ bez zmuszania użytkownika do ręcznego dodawania pliku .obj do jego projektu, możesz użyć dyrektywy Delphi {$HPPEMIT}. Mówi ona kompilatorowi Delphi, aby wstrzyknął konkretną dyrektywę C++ #pragma link do wygenerowanego pliku .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.
Gdy C++Builder włącza lxXlsSummary.hpp, kompilator napotyka #pragma link i automatycznie mówi linkerowi (ILINK32/ILINK64), aby rozwiązał symbole z lxXlsSummary.obj
Złota zasada utrzymania komponentów
Aby całkowicie uniknąć psucia kompilacji C++Builder, musisz przyjąć rygorystyczną politykę rejestracji. Ilekroć nowy moduł Pascal jest dodawany do twojej biblioteki, musi zostać jawnie zarejestrowany jednocześnie we wszystkich trzech typach plików projektu
1. Zaktualizuj projekt C++Builder (.cbproj / .bpk)
Otwórz plik .cbproj w edytorze tekstu i dodaj nowy moduł do listy kompilacji, upewniając się, że podajesz unikalną kolejność budowania. Jeśli używasz starszych wersji C++Builder z plikami .bpk, upewnij się, że dodany jest znacznik <file containerid="PascalCompiler" designclass="" filename="lxXlsSummary.pas" formname="" localcommand="" unitname="lxXlsSummary"></file>
<DelphiCompile Include="lxXlsSummary.pas">
<BuildOrder>101</BuildOrder>
</DelphiCompile>
2. Zaktualizuj pakiet Delphi (.dpk)
Dodaj moduł do jawnej klauzuli contains. Zapewnia to, że kompilator Delphi nie musi polegać na niejawnym linkowaniu, które i tak jest ogólnie uważane za złą praktykę
package HotXLS;
{$R *.res}
{$ALIGN 8}
{$ASSERTIONS ON}
{$BOOLEVAL OFF}
requires
rtl,
vcl;
contains
lxRead in 'lxRead.pas',
lxXlsSummary in 'lxXlsSummary.pas';
end.
Walidacja ciągłej integracji
Ostateczną obroną przed tą pułapką jest walidacja CI/CD. Nigdy nie polegaj wyłącznie na udanym budowaniu w Delphi przed wysłaniem komponentu dwujęzycznego. Twoje skrypty budowania muszą wywoływać MSBuild lub narzędzia wiersza poleceń bcc32c na projektach C++Builder (np. build-Win32-Lib-CB.cmd) i przeprowadzać pełne linkowanie wersji próbnej C++ oraz pełnych wersji demonstracyjnych. Dopiero gdy linker C++ zakończy się sukcesem, możesz mieć pewność, że wszystkie moduły Delphi są poprawnie zarejestrowane i udostępniają swoje symbole środowisku uruchomieniowemu C++
Uwaga: zgodność kompilatorów międzyplatformowych jest ściśle utrzymywana w edycjach Delphi i C++Builder komponentu HotXLS VCL Component