Το Free Pascal σε Win32 βάζει αυτόματα underscore μπροστά σε κάθε import cdecl; external, ενώ το public name εξάγει τη συμβολοσειρά που γράψατε, χαρακτήρα προς χαρακτήρα. Το HotPDF πρέπει να ικανοποιεί και τις δύο συμβάσεις στο ίδιο source tree, γιατί το build του Delphi έρχεται ήδη με δηλώσεις imports που γράφουν το underscore με το χέρι. Το να κάνεις λάθος σε αυτή την ασυμμετρία παράγει link errors που κατονομάζουν ένα symbol που δεν έγραψε κανείς
Η επέκταση μιας βιβλιοθήκης Delphi σε Free Pascal συνήθως περιγράφεται ως πρόβλημα φορητότητας, και σε Win64 ως επί το πλείστον είναι. Το Win32 είναι άλλη υπόθεση. Το ABI των Windows σε 32-bit x86 κουβαλά τριάντα χρόνια συσσωρευμένης σύμβασης για το πώς γράφονται τα C symbols, ποιος καθαρίζει τη στοίβα, και ποια compiler-private helpers επιτρέπεται να υποθέτει μια translation unit, και καθένα από αυτά είναι σημείο όπου δύο Pascal compilers που συμφωνούν στη γλώσσα μπορούν ακόμα να διαφωνούν στο object file
Γιατί το ίδιο symbol επιλύεται σε Win64 και αποτυγχάνει σε Win32;
Γιατί το πρόθεμα underscore είναι σύμβαση 32-bit που το Free Pascal εφαρμόζει στα imports αλλά όχι στα exports. Δηλώστε function deflate(...): Integer; cdecl; external; και το FPC ψάχνει _deflate στο object file σε Win32, και deflate σε Win64. Είναι σωστή συμπεριφορά και ταιριάζει με όσα εκπέμπει ένας C compiler. Η παγίδα είναι στην άλλη πλευρά της γέφυρας: μια ρουτίνα με public name 'deflate' εξάγει ακριβώς deflate και στους δύο targets, χωρίς κανένα πρόθεμα
Τώρα προσθέστε την ιστορική λεπτομέρεια που το κάνει συγκεκριμένο. Το build του Delphi δηλώνει ήδη μερικά από αυτά τα σημεία εισόδου με το underscore γραμμένο μέσα στο όνομα, γιατί αυτό περιέχουν τα δικά του object files. Δώστε την ίδια δήλωση στο FPC σε Win32 και ο compiler ευλαβικά βάζει πάλι πρόθεμα, οπότε ο linker κυνηγά __deflate, ένα symbol που δεν εξάγει τίποτα. Η διαισθητική λύση, να προστεθεί ένα underscore παντού, σπάει τα imports που ήταν ήδη γραμμένα σωστά
Αυτό που δουλεύει είναι ένα ζευγάρι σταθερών προθέματος και όχι ένα. Τα HPDFFPCZLib και HPDFFPCCodecStubs χρησιμοποιούν ένα πρόθεμα για τα σκέτα C imports και άλλο για imports που κουβαλούν ήδη πρόθεμα από την πλευρά του Delphi, και σε Win64 και οι δύο σταθερές είναι κενές ώστε τα υπάρχοντα link names να επιβιώνουν ανέγγιχτα. Δύο σταθερές αντί για μία είναι όλη η λύση, και γίνεται προφανής μόνο αφού χωρίσετε τον κανόνα των imports από τον κανόνα των exports
// Δύο προθέματα, όχι ένα: τα σκέτα C imports και τα imports που κουβαλούν
// ήδη χειρόγραφο πρόθεμα Delphi διακοσμούνται διαφορετικά κάτω από FPC/Win32
const
{$IF DEFINED(FPC) and DEFINED(CPU32)}
CPrefix = '_'; // το βάζει το FPC μόνο του για cdecl external
DelphiCName = ''; // ήδη γραμμένο με το underscore στον πηγαίο
{$ELSE}
CPrefix = '';
DelphiCName = '';
{$IFEND}
// Export side: το 'public name' είναι κατά γράμμα σε κάθε target
procedure hpdf_codec_free(P: Pointer); cdecl;
public name 'hpdf_codec_free';
Το WIN32 σας λέει την αρχιτεκτονική, όχι το ABI
Αυτό είναι το λάθος μεταγλώττισης υπό συνθήκη με την πιο μακριά ουρά debugging, και αξίζει να διατυπωθεί καθαρά: τα WIN32 και WIN64 περιγράφουν την αρχιτεκτονική στόχο και δεν λένε τίποτα για το ποια compiler-private runtime helpers υπάρχουν. Το Free Pascal ορίζει και τα δύο symbols στους αντίστοιχους Windows targets, ακριβώς όπως το Delphi. Ένα guard τύπου {$IFDEF WIN32} γύρω από κώδικα που καλεί Delphi runtime helper άρα μεταγλωττίζεται κάτω από FPC και αποτυγχάνει τη στιγμή της σύνδεσης
Συγκεκριμένα, τρεις οικογένειες κώδικα πέφτουν στην παγίδα. Τα trampolines 64-bit ακεραίων του Delphi που φτάνεις μέσω helpers System.@_ll, οι ρουτίνες υποστήριξης assembly MSVC Win32, και οι θέσεις imports που τα συνοδεύουν υπάρχουν όλα για να εξυπηρετούν precompiled C objects που συνδέει το build του Delphi. Το Free Pascal δεν συνδέει εκείνα τα objects, οπότε δεν χρειάζεται καθόλου εκείνη τη μηχανολογία, και κάθε αναφορά σε αυτή πρέπει να εξαφανιστεί. Η λεπτότητα είναι ότι δήλωση και υλοποίηση πρέπει να αποκλειστούν μαζί. Αποκλείστε μόνο το ένα και ο compiler αναφέρει κάτι άχρηστο για ένα αναγνωριστικό που δεν ταυτίζει με τίποτα
Ο κανόνας που βγαίνει είναι σύντομος. Φράξτε με τον compiler όταν η ερώτηση αφορά ABI ή υποστήριξη runtime, φράξτε με την αρχιτεκτονική όταν η ερώτηση αφορά πλάτος δείκτη ή αριθμό registers, και μην αφήσετε ποτέ το ένα να αντικαταστήσει το άλλο
Φράγματα σε δηλώσεις και υλοποιήσεις μαζί
Ένα μπλοκ συνθήκης σε τμήμα interface είναι εύκολο να πέσετε μέσα του χωρίς να το καταλάβετε, και το μήνυμα σφάλματος που προκύπτει δείχνει παντού εκτός από την αιτία. Προσθέτετε μια δήλωση μεθόδου σε class interface και το φυσικό σημείο είναι δίπλα στις συγγενικές μεθόδους, κάτι απολύτως καλό μέχρι τη στιγμή που εκείνοι οι γείτονες τυχαίνει να κάθονται μέσα σε υπάρχον μπλοκ {$IFDEF}. Οι conditional directives δεν έχουν εσοχές, οπότε ένα μπλοκ που άνοιξε σαράντα γραμμές πάνω είναι ουσιαστικά αόρατο όσο διαβάζετε τις γύρω δηλώσεις
Αυτό που έπεται είναι μια μεταγλώττιση που περνά σε ένα toolchain και παράγει καταρράκτη σε άλλο. Αν το γύρω guard είναι έλεγχος έκδοσης Delphi που το Free Pascal δεν ικανοποιεί, η δήλωση εξαφανίζεται για FPC ενώ η άνευ όρων υλοποίηση μένει, και ο compiler αναφέρει μακρύ κατάλογο παραπόνων για αναγνωριστικά μεθόδων που περίμενε και δεν βρήκε. Κανένα από τα μηνύματα δεν αναφέρει το μπλοκ συνθήκης που το προκάλεσε
Δύο συνήθειες προλαμβάνουν όλη αυτή την κατηγορία αποτυχίας. Πριν από εισαγωγή σε τμήμα interface, κοιτάξτε προς τα πάνω για την πλησιέστερη ανοιχτή συνθήκη αντί να εμπιστευτείτε την οπτική ομαδοποίηση. Και μεταχειριστείτε ένα πράσινο test suite Delphi ως απόδειξη μόνο για το Delphi: το build της βιβλιοθήκης Free Pascal είναι ξεχωριστή πύλη, και ο μόνος τρόπος να ξέρετε ότι περνά είναι να τρέξετε τα build-Win32-Lib-FPC.cmd και build-Win64-Lib-FPC.cmd ως μέρος της ίδιας αλλαγής
Τι σπάει στον κώδικα αριθμητικής 32-bit
Ένας γλωσσικός περιορισμός εμφανίζεται ακριβώς στον κώδικα που θέλει λιγότερο να αλλάξει: το Free Pascal 32-bit δεν δέχεται UInt64 ως μεταβλητή ελέγχου βρόχου for. Στις μονάδες ελλειπτικών καμπυλών που κουβαλούν τα X25519 και X448, οι βρόχοι που διαπερνούν πίνακες limbs γράφτηκαν με μετρητές 64-bit απλώς επειδή όλα τα άλλα στο αρχείο είναι 64-bit
Η λύση πρέπει να είναι χειρουργική, γιατί στην αριθμητική σωμάτων το πλάτος μιας μεταβλητής είναι μέρος του επιχειρήματος ορθότητας. Οι δείκτες βρόχων γίνονται Integer, αφού ένας πίνακας limbs έχει μια χουφτά στοιχεία και κανείς δείκτης δεν πλησιάζει ποτέ το εύρος 32-bit. Ό,τι συμμετέχει στην αριθμητική, τα ίδια τα limbs, η διάδοση carries και οι μάσκες, μένει UInt64, γιατί η στένωση οποιουδήποτε από αυτά αλλάζει σιωπηλά το αποτέλεσμα modulo του πρώτου του σώματος
// Το 32-bit FPC απορρίπτει μεταβλητή βρόχου UInt64. Στενέψτε μόνο τον
// δείκτη· limbs, μάσκες και carries κρατούν το πλάτος τους ή αλλάζει η αριθμητική σώματος
var
I: Integer; // ήταν UInt64
Carry, Mask: UInt64;
begin
Carry := 0;
for I := 0 to High(Limbs) do
begin
Limbs[I] := Limbs[I] + Carry;
Carry := Limbs[I] shr 51;
Limbs[I] := Limbs[I] and Mask;
end;
end;
Η επαλήθευση για μια τέτοια αλλαγή δεν μπορεί να είναι test επαναφοράς. Η κρυπτογράφηση και αποκρυπτογράφηση με την ίδια χαλασμένη υλοποίηση συμφωνεί απόλυτα με τον εαυτό της, γι αυτό τα known-answer vectors είναι μη διαπραγματεύσιμα εδώ: τρέξτε τα δημοσιευμένα test vectors των X25519 και X448 και συγκρίνετε τα ακριβή bytes εξόδου. Είναι ο μόνος έλεγχος που διαχωρίζει σωστή υλοποίηση από αυτοσυνεπή λάθος, και ισχύει εξίσου για τα συμμετρικά primitives που συζητιούνται στα όρια codec deflate και AES του Free Pascal
Τι αξίζει ένα build Win32 Free Pascal
Το πρακτικό όφελος είναι ότι μια εφαρμογή Lazarus για 32-bit Windows παίρνει την ίδια μηχανή εγγράφων με την αντιστοιχη της σε Delphi, χωρίς ξεχωριστό δυαδικό συμβόλαιο προς συντήρηση. Αυτό μετράει περισσότερο για τα deployments που σπάνια συζητιούνται: βιομηχανικοί ελεγκτές, τερματικά point-of-sale και μακρόβιο λογισμικό επιχειρησιακών γραμμών όπου το 32-bit runtime δεν είναι legacy επιλογή αλλά υλικός περιορισμός
Η ιστορία του Win64 προηγήθηκε και περιγράφεται στη υποστήριξη Free Pascal και Lazarus σε Win64. Το Win32 δεν είναι επανάληψή της. Το Win64 έχει ένα calling convention, καθόλου name decoration και κανένα Delphi-private integer helper προς παρακάμψη, οπότε σχεδόν όλα σε αυτό το άρθρο είναι ειδικά για τον 32-bit target. Οι μονάδες αριθμητικής που χρειάστηκαν την αλλαγή μεταβλητής βρόχου είναι οι ίδιες που περιγράφονται στη αριθμητική Montgomery πάνω στις καμπύλες NIST, όπου η πειθαρχία πλάτους εξηγείται πιο βαθιά
Το γενικό δίδαγμα είναι ότι η δουλειά φορητότητας μεταξύ compilers δεν αφορά πρωτίστως χαρακτηριστικά γλώσσας. Και οι δύο compilers δέχονται το ίδιο Object Pascal εδώ. Αλλάζει το object file: πώς γράφονται τα symbols, ποιες ρουτίνες helpers υποτίθεται ότι παρέχει το runtime, και ποια precompiled objects είναι στη σύνδεση. Το HotPDF παραδίδει τα πακέτα Free Pascal και Lazarus δίπλα στα Delphi και C++Builder στο HotPDF Delphi PDF component, οπότε το ίδιο source tree τροφοδοτεί κάθε toolchain αντί να διακλαδίζεται ανά compiler