Τεχνικό Άρθρο

Διαγλωσσική Μεταγλώττιση Στοιχείων Delphi για C++Builder: Αποφυγή Ανεπίλυτων Εξωτερικών

Κατά τη συντήρηση ενός στοιχείου 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)

Σύγκριση ροής build για το στοιχείο HotXLS Delphi: το dcc32 του Delphi μεταγλωττίζει σιωπηρά μια μη καταχωρημένη μονάδα στο πακέτο και χτίζει με πράσινο, ενώ το MSBuild του C++Builder δεν παράγει ποτέ το object αρχείο του και το ILINK32 ματαιώνεται με unresolved external
Η ίδια μη καταχωρημένη μονάδα ακολουθεί δύο αποκλίνουσες διαδρομές: το dcc32 καλύπτει την παράλειψη μέσω έμμεσης στατικής σύνδεσης ενώ ο αυστηρότερος linker του C++Builder την εκθέτει

Επίλυση Εξωτερικών με 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

Ροή τεσσάρων στηλών του μηχανισμού συνδέσμου pragma HPPEMIT σε Delphi: μια δηλωμένη οδηγία κάνει το dcc32 να εγχύσει τη σειρά linker στο lxXlsSummary.hpp, το C++Builder την καταναλώνει στο include, και το ILINK32 επιλύει το XlsReadSummaryInformation αυτόματα
Η σειρά linker γράφεται μία φορά σε πηγή Pascal, ταξιδεύει μέσα στην παραγόμενη κεφαλίδα, και φτάνει σε κάθε μελλοντικό build C++ χωρίς χειροκίνητες επεξεργασίες έργου

Ο Χρυσός Κανόνας για τη Συντήρηση Στοιχείων

Για να αποφύγετε την πλήρη καταστροφή των μεταγλωττίσεων στο 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