Το PDFlibPas κάνει build και με Free Pascal για Windows 32-bit, και το δύσκολο κομμάτι δεν ήταν ποτέ η Pascal. Ήταν τα object files: τα objects των AES και OpenJPEG που συνδέει το build του Delphi είναι OMF, ο internal linker του Free Pascal απαιτεί COFF, και η μετατροπή ανάμεσα στα δύο παράγει ονόματα sections και section-definition symbols που οδηγούν τον linker σε internal errors αντί για διαγνωστικά μηνύματα
Όποιος έχει συνδέσει ποτέ C objects σε βιβλιοθήκη Pascal ξέρει το έδαφος. Το Win64 είναι συγκριτικά πολιτισμένο: ένα object format, ένα calling convention, καθόλου name decoration. Το Win32 κρατά κάθε στρώμα ιστορίας που συσσώρευσε η πλατφόρμα, και μια βιβλιοθήκη που κάνει static linking τρίτου C κώδικα τα συναντά όλα μαζί
Ο κατάλογος του compiler δεν σας λέει το target
Ξεκινήστε από το σημείο εισόδου του build, γιατί ένα λάθος εδώ καίει ώρες προτού μπει κανένα object file στη μέση. Το όνομα ενός καταλόγου εγκατάστασης του Free Pascal λέει πού μένει ο κύριος compiler, όχι τι παράγει. Ένας host compiler 32-bit μπορεί να καλέσει έναν cross-compiler που κάθεται δίπλα του και να εκπέμψει 64-bit κώδικα αν περάσετε τα σωστά target switches, οπότε η συμπερασματολογία από ένα path είναι μαντεψιά που τυχαίνει να δουλεύει μέχρι κάποιος να αναδιαρθρώσει το toolchain του
Η αξιόπιστη προσέγγιση είναι να ρωτήσετε τον compiler. Ζητήστε τον πραγματικό επεξεργαστή και λειτουργικό σύστημα του target μέσα από τα information switches του ίδιου, και δεχτείτε και τα δύο συνηθισμένα layouts εγκατάστασης, τον flat κατάλογο binaries και τον εμφωλευμένο ανά έκδοση, γιατί διαφορετικά installers και εργαλεία διαχείρισης toolchain παράγουν διαφορετικά σχήματα. Ένα build script που hard-codes ένα από τα δύο layouts δουλεύει σε ακριβώς μία μηχανή
Γιατί ένα μετατραπημένο object file σπάει τον internal linker;
Γιατί η μετατροπή διατηρεί τη σύμβαση ονοματοδοσίας sections του OMF και συνθέτει section-definition symbols που δεν ταιριάζουν με όσα περιμένει ο COFF linker. Η μετατροπή των OMF objects σε COFF είναι απαραίτητη αλλά όχι αρκετή: τα αρχεία που προκύπτουν κουβαλούν τα κλασικά _TEXT, _DATA και _BSS ονόματα sections, συν ονόματα section-definition symbols που βγαίνουν από αυτά, και αν τα ταΐσετε στον internal linker του Free Pascal παίρνετε internal compiler errors αντί για μήνυμα για την ονοματοδοσία των sections
Ένα internal error είναι η χειρότερη μορφή αποτυχίας για πρόβλημα build, γιατί δεν λέει τίποτα για το τι έφταιγε στην είσοδο. Η λύση είναι ένα στάδιο κανονικοποίησης μετά τη μετατροπή, πάνω στο COFF αρχείο: επανεγγραφή των ονομάτων sections στη μορφή που περιμένεται και αντίστοιχη επανεγγραφή των section-definition symbols, αφήνοντας ανέπαφα τον symbol index, τα bytes του κώδικα και τις relocations. Αυτός ο τελευταίος περιορισμός είναι όλη η δυσκολία. Μια επανεγγραφή που αναριθμεί symbols ή μετακινεί offsets βγάζει ένα object που συνδέεται και μετά κρασάρει
Ένα προκαταρκτικό βήμα αφορά ένα από τα δύο object sets. Τα OpenJPEG objects που χτίζει ο κλασικός 32-bit compiler της C++ εξαρτώνται από ιδιωτικά helper routines του Delphi για 64-bit ακέραιους, που το Free Pascal δεν παρέχει, οπότε καμία ποσότητα μετατροπής μορφής δεν τα κάνει χρήσιμα. Αυτά ξαναχτίζονται πρώτα με τον compiler βάσει Clang, που δεν εκπέμπει αυτές τις εξαρτήσεις, και μετατρέπονται κατόπιν
// Τα objects για τον FPC target μένουν στον δικό τους κατάλογο. Δεν
// αντικαθιστούν το object set του Delphi, επειδή και τα δύο toolchains
// χτίζουν από το ίδιο source tree και το καθένα θέλει τα δικά του link inputs
//
// Lib\thirdparty\Win32 OMF objects του Delphi, αμετάβλητα
// Lib\thirdparty\Win32f COFF objects για FPC, μετατραπέντα και κανονικοποιημένα
//
// Σημεία εισόδου build:
// build-Win32-Lib-FPC.cmd
// build-Win64-Lib-FPC.cmd
Τα compiler-private helpers δεν είναι φορητά, και οι συμβάσεις τους επίσης
Το runtime του Delphi παρέχει assembly trampolines για πράξεις 64-bit ακεραίων σε 32-bit x86, και τα precompiled C objects που χτίζονται για Delphi τα καλούν. Το Free Pascal έχει δική του διάταξη, οπότε αυτές οι αναφορές πρέπει να ικανοποιηθούν αλλιώς και όχι με ανακατεύθυνση. Η λεπτομέρεια που κάνει την ανακατεύθυνση αδύνατη είναι το calling convention: το timing helper που χρησιμοποιεί ο κώδικας απεικόνισης έχει το τετρασήμαντο όρισμά του καθαρισμένο από τον callee, ενώ το helper της 64-bit διαίρεσης καθαρίζει δεκαέξι bytes και επιστρέφει το αποτέλεσμα στο κλασικό ζεύγος registers. Δύο helpers, δύο συμβάσεις, και ένα trampoline γραμμένο για το ένα χαλάει σιωπηλά τη στοίβα για το άλλο
Το name decoration προσθέτει το δεύτερο μισό του προβλήματος. Στο Win32 το Free Pascal βάζει αυτόματα underscore μπροστά από εξωτερικά C imports ενώ εξάγει τις δηλώσεις public name κατά γράμμα, οπότε η πλευρά εισαγωγής και η πλευρά εξαγωγής της ίδιας γέφυρας ακολουθούν διαφορετικούς κανόνες. Η γέφυρα C runtime που χρειάζεται το OpenJPEG πρέπει επομένως να εξάγει τα ακριβή ονόματα C symbols, και τα variadic σημεία εισόδου θέλουν έμμεσο 32-bit jump και όχι ευθύ. Τίποτα από αυτά δεν είναι εξωτικό αφού το διατυπώσεις. Όλα αποτυγχάνουν ως link error που κατονομάζει ένα symbol που δεν έγραψε κανείς
Τι έκανε ένα εκτελέσιμο Win32 να πεθαίνει πριν το main;
Μια DLL 64-bit στο search path, στην οποία φτάσατε επειδή η μονάδα zlib του Free Pascal κάνει dynamic binding αντί για static linking. Το σύμπτωμα ήταν άμεση έξοδος με τον κωδικό κατάστασης invalid-image, προτού τρέξει οποιοσδήποτε Pascal κώδικας του προγράμματος, κάτι που σας στέλνει να ψάχνετε στο πρόγραμμα που μόλις χτίσατε ενώ το φταίξιμο είναι στον loader που επιλύει ένα import προς τη λάθος αρχιτεκτονική
Το δίδαγμα αφορά τις υποθέσεις, όχι τη zlib. Μια μονάδα που φέρει όνομα βιβλιοθήκης συμπίεσης δεν είναι καθόλου σίγουρο ότι την περιέχει· μπορεί να είναι binding που περιμένει shared library την ώρα της εκτέλεσης, και μια δυναμική εξάρτηση που δεν θέλατε είναι βάρος στο deployment ακόμα κι αν τυχαίνει να επιλύεται. Η μετάβαση στην καθαρή Pascal υλοποίηση streams δίνει και στους δύο targets ένα statically ενταγμένο μονοπάτι συμπίεσης χωρίς καμία εξωτερική εξάρτηση, που είναι ακριβώς όσο θα έπρεπε να έχει εξαρχής μια βιβλιοθήκη που ενσωματώνεται στην εφαρμογή άλλου
Το ίδιο ένστικτο ισχύει για το external backend του JBIG2 encoder. Στον 32-bit target ο εξωτερικός encoder δεν συνδέεται, οπότε τα αιτήματα πέφτουν στον ενσωματωμένο Pascal encoder, και το test που το επαληθεύει πρέπει να ελέγχει την κατάσταση registration του τρέχοντος target αντί να θεωρεί ένα επιτυχημένο encode απόδειξη ότι ο external backend υπάρχει. Ένα fallback που δουλεύει είναι ακριβώς το πράγμα που κρύβει μια χαμένη εξάρτηση, το πρότυπο αποτυχίας που εξετάζεται στη διάγνωση σιωπηλών αποτυχιών stub. Η δουλειά του 64-bit static linking καλύπτεται στο static linking του jbig2enc κάτω από FPC
Αριθμητική 32-bit πάνω σε memory stream
Κώδικας που χειρίζεται μεγέθη buffers με unsigned αριθμητική πλάτους δείκτη είναι σωστός σε Win64 και μία μεγάλη εικόνα μακριά από overflow σε Win32. Το in-memory stream που τροφοδοτεί τον codec του JPEG 2000 μεγαλώνει με διπλασιασμό και προχωρά με πρόσθεση, και σε 32-bit target και οι δύο πράξεις μπορούν να κάνουν wrap για εισόδους μεγάλες αλλά απολύτως νόμιμες
Κάθε write, skip, seek και αρχική allocation επομένως ελέγχει προ του υπολογισμού, και το ταβάνι χωρητικότητας είναι η μέγιστη signed τιμή πλάτους δείκτη, διαλεγμένη να ταιριάζει με όσα μπορούν να εκφράσουν η ρουτίνα μετακίνησης blocks και οι τιμές επιστροφής του callback. Η συμπεριφορική απαίτηση όταν ένα αίτημα απορρίπτεται είναι εύκολο να βγει λάθος: η άρνηση δεν πρέπει να αλλάζει ούτε τη θέση του stream ούτε το μήκος του. Μια μερική μετάλλαξη ακολουθούμενη από σφάλμα αφήνει το stream σε κατάσταση που ο caller δεν μπορεί να λογαριαστεί, και η επόμενη πράξη το χειροτερεύει
// Έλεγξε πριν υπολογίσεις. Σε Win32 και οι δύο πράξεις κάνουν wrap
// για εισόδους που παράγει νόμιμα μια μεγάλη εικόνα JPEG 2000
if Needed > NativeUInt(High(NativeInt)) - FPosition then
Exit(False); // άρνηση, άφησε θέση και μέγεθος ως έχουν
NewCapacity := FCapacity;
while (NewCapacity < FPosition + Needed) do
begin
if NewCapacity > NativeUInt(High(NativeInt)) shr 1 then
Exit(False); // ο διπλασιασμός θα έκανε overflow
NewCapacity := NewCapacity shl 1;
end;
Δύο παγίδες στα build outputs που επιβιώνουν του port
Ο διαχωρισμός των εκτελέσιμων των tests και των samples ανά αρχιτεκτονική target σε καταλόγους εξόδου ανά target είναι προφανώς σωστός και σπάει αμέσως οτιδήποτε εντόπιζε τα test data του μετρώντας επίπεδα καταλόγων προς τα πάνω. Η λύση είναι η αναζήτηση του καταλόγου assets προς τα πάνω αντί για σταθερό βάθος, με έναν σκόπιμο περιορισμό: το signing sample δέχεται certificate fallback μόνο από τον δικό του κατάλογο project, ποτέ από τυχαίο πρόγονο, γιατί ένα ομώνυμο certificate πιο ψηλά στο δέντρο είναι έκπληξη ασφαλείας και όχι ευκολία
Η δεύτερη παγίδα επιβιώνει κάθε port και αξίζει να τη μεταφέρετε σε οποιοδήποτε FPC project. Μετά από αναβάθμιση compiler, το να απορρίπτει ο compiler τα ξεπερασμένα PPU files δεν φτάνει, γιατί ο linker εξακολουθεί να προτιμά object files που έχουν μείνει στο unit search path ακόμα κι όταν το PPU που φόρτωσε ήρθε από τον σωστό κατάλογο, και η προσθήκη ρητού object output path δεν ανατρέπει αυτή την προτίμηση. Η μόνη αξιόπιστη απάντηση είναι φρέσκος προσωρινός κατάλογος μονάδων ανά γύρο build. Οτιδήποτε λιγότερο βγάζει binary συνδεδεμένο από δύο εκδόσεις compiler, που αποτυγχάνει με τρόπους που μοιάζουν με source bugs
Τα platform conditionals είναι το τελευταίο κομμάτι, και η επιλογή του σωστού άξονα μετράει περισσότερο απ όσο φαίνεται. Η σωστή ερώτηση είναι συνήθως αν ο κώδικας είναι Windows-specific και όχι αν υπάρχει μια συγκεκριμένη βιβλιοθήκη widgets, όπως έδειξε η δουλειά της μετατροπής metafiles στο EMF vector import και platform conditionals: η αλλαγή εκείνου του guard από συνθήκη control-library σε συνθήκη platform μετέτρεψε μια υποτιθέμενη επανεγγραφή σε αλλαγή μίας directive. Η υποστήριξη Free Pascal και Lazarus και για τα δύο Windows targets έρχεται μαζί με το PDFlibPas Delphi PDF library, χτισμένο από τις ίδιες πηγές με τα πακέτα Delphi και C++Builder