Κατά τη συντήρηση ενός στοιχείου VCL που είναι γραμμένο σε Delphi αλλά χρησιμοποιείται τόσο από χρήστες Delphi όσο και από χρήστες C++Builder, συνειδητοποιείτε γρήγορα ότι τα δύο συστήματα μεταγλώττισης (build systems) αντιμετωπίζουν τις εξαρτήσεις πολύ διαφορετικά. Μια μονάδα (unit) που μεταγλωττίζεται τέλεια στο Delphi μπορεί να προκαλέσει καταστροφικά σφάλματα σύνδεσης (linker errors) στο C++Builder. Αυτή η ασυμφωνία είναι μια συχνή παγίδα για τους δημιουργούς στοιχείων, ιδιαίτερα κατά την προσθήκη νέων εσωτερικών μονάδων σε μια υπάρχουσα βιβλιοθήκη
Ακολουθεί μια λεπτομερής ανάλυση του γιατί συμβαίνει αυτό, χρησιμοποιώντας πραγματικά παραδείγματα από το στοιχείο HotXLS, και του πώς να θωρακίσετε τη διαδικασία διαγλωσσικής μεταγλώττισής σας (cross-compilation) χρησιμοποιώντας ρητές συμπεριλήψεις, την οδηγία {$HPPEMIT} και τη σύνδεση μέσω pragma
Η Παγίδα: Έμμεση Μεταγλώττιση έναντι Ρητής Συμπερίληψης
Ας υποθέσουμε ότι δημιουργείτε μια νέα μονάδα Delphi, την lxXlsSummary.pas, για τον χειρισμό των μεταδεδομένων εγγράφου, και την συμπεριλαμβάνετε (uses) στην κύρια μονάδα ανάλυσης, lxRead.pas. Πατάτε μεταγλώττιση στο Delphi IDE, η διαδικασία ολοκληρώνεται με επιτυχία (πράσινο) και κυκλοφορείτε την ενημέρωση
Την επόμενη μέρα, οι χρήστες σας του 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. Όταν ο linker προσπαθεί να επιλύσει τις κλήσεις που γίνονται από το lxRead.obj, τα σύμβολα λείπουν, οδηγώντας σε σφάλμα ανεπίλυτου εξωτερικού (unresolved external)
Επίλυση Εξωτερικών με Pragma Link και HPPEMIT
Εάν θέλετε να βεβαιωθείτε ότι μια μονάδα έχει συνδεθεί σωστά στη C++ χωρίς να αναγκάσετε τον χρήστη να προσθέσει χειροκίνητα το αρχείο .obj στο έργο του, μπορείτε να χρησιμοποιήσετε την οδηγία {$HPPEMIT} του Delphi. Αυτό λέει στον μεταγλωττιστή του Delphi να εισαγάγει μια συγκεκριμένη οδηγία #pragma link της C++ στο παραγόμενο αρχείο .hpp
unit lxXlsSummary;
interface
{$IFDEF WINDOWS}
// Εισαγωγή ενός pragma link στο παραγόμενο αρχείο κεφαλίδας C++
// Αυτό αναγκάζει τον linker της C++ να συμπεριλάβει το αντίστοιχο αρχείο .obj
{$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;
// Λογική εξαγωγής μεταδεδομένων εδώ
end;
end.
Όταν ο C++Builder συμπεριλάβει το lxXlsSummary.hpp, ο μεταγλωττιστής συναντά το #pragma link και ενημερώνει αυτόματα τον linker (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 πριν κυκλοφορήσετε ένα στοιχείο διπλής γλώσσας. Τα σενάρια μεταγλώττισής σας (build scripts) πρέπει να καλούν το MSBuild ή τα εργαλεία γραμμής εντολών bcc32c στα έργα του C++Builder (π.χ., build-Win32-Lib-CB.cmd) και να εκτελούν μια πλήρη σύνδεση (link) των δοκιμαστικών (trial) και πλήρων παρουσιάσεων (demos) της C++. Μόνο όταν ο linker της C++ επιτύχει μπορείτε να είστε βέβαιοι ότι όλες οι μονάδες Delphi έχουν καταχωρηθεί σωστά και εκθέτουν τα σύμβολά τους στον χρόνο εκτέλεσης της C++
Σημείωση: Η διαπλατφορμική συμβατότητα μεταγλωττιστή διατηρείται αυστηρά σε όλες τις εκδόσεις Delphi και C++Builder του HotXLS Delphi VCL Component