Το στοιχείο PDFium βρίσκει την εγγενή βιβλιοθήκη του μέσω μιας σταθερής, διατεταγμένης αλυσίδας αναζήτησης αντί να το αφήσει στον loader του λειτουργικού συστήματος, επειδή ένα δέντρο ανάπτυξης που είναι ρητό είναι δέντρο ανάπτυξης που μπορείτε να αποσφαλματώσετε. Σε Windows εκείνη η αλυσίδα ψάχνει έναν υποκατάλογο Win32 ή Win64 που ήδη αποστέλλει ο installer. Σε άλλους στόχους χτίζει το όνομα του υποκαταλόγου από τα macros στόχου Free Pascal, ως <cpu>-<os>, ώστε το δέντρο ανάπτυξης να διαβάζεται ακριβώς όπως το δέντρο μεταγλωττισμένων μονάδων. Εκείνη η τελευταία απόφαση εισήγαγε ένα σφάλμα που αξίζει ολόκληρο το άρθρο, επειδή η αιτία ήταν ένα κεφαλαίο γράμμα και το σύμπτωμα ήταν σιωπή
Η αλυσίδα, με τη σειρά
Τέσσερις τοποθεσίες, δοκιμασμένες με τη σειρά, και μετά ο loader πλατφόρμας ως έσχατη λύση. Πρώτα η προτιμώμενη διάταξη, ένας κατάλογος DLLs δίπλα στο εκτελέσιμο που περιέχει έναν υποκατάλογο ανά στόχο. Δεύτερον μια εναλλακτική διάταξη με τον υποκατάλογο στόχου απευθείας δίπλα στο εκτελέσιμο. Τρίτον η επίπεδη κληρονομική διάταξη, η βιβλιοθήκη να κάθεται δίπλα στο εκτελέσιμο χωρίς καθόλου υποκατάλογο. Τέταρτο, μόνο σε Windows, ο κατάλογος συστήματος, που χρειάζεται προσοχή επειδή μια διεργασία 32-bit πρέπει να κοιτάξει στο SysWOW64 και μια διεργασία 64-bit στο System32, και σε 32-bit Windows ο πρώτος δεν υπάρχει οπότε η αναζήτηση πρέπει να πέσει πίσω. Μόνο μετά από όλα αυτά ο loader ρωτιέται να ψάξει μόνος του
Δεν υπάρχει σκόπιμα βήμα καταλόγου συστήματος εκτός Windows. Η δική της διαδρομή αναζήτησης του loader πλατφόρμας, που οδηγείται από τη ρύθμιση του runtime linker και το περιβάλλον διαδρομών βιβλιοθηκών, καλύπτει ήδη εκείνο το έδαφος, και η αναπαραγωγή της σε Pascal θα σήμαινε επανυλοποίηση κανόνων που διαφέρουν ανά διανομή. Η διάγνωση αποτυχιών στην αλυσίδα Windows καλύπτεται χωριστά στο άρθρο για την ανάπτυξη του DLL PDFium και τη διάγνωση αστοχιών φόρτωσης
Από πού προέρχεται το όνομα του υποκαταλόγου
Σε Windows είναι Win32 ή Win64, αποφασισμένο από το bitness της τρέχουσας διεργασίας και όχι του λειτουργικού συστήματος, επειδή εκείνο καθορίζει ποιο δυαδικό αρχείο μπορεί να φορτωθεί. Παντού αλλού το όνομα χτίζεται από τα macros στόχου του μεταγλωττιστή ώστε ένα μηχάνημα που χτίζει για δύο αρχιτεκτονικές να παράγει δύο καθαρά διαχωρισμένα δέντρα, και ώστε ο φάκελος που φιλοξενεί την εγγενή βιβλιοθήκη να κάθεται δίπλα στον φάκελο που φιλοξενεί τις μεταγλωττισμένες μονάδες με το ίδιο όνομα
function BuildDllSubDir(UseV8: Boolean): string;
begin
{$IFDEF MSWINDOWS}
if IsWin64 then
Result := 'Win64'
else
Result := 'Win32';
{$ELSE}
// Τα macros του μεταγλωττιστή βάζουν αρχικό κεφαλαίο στο OS («Linux»,
// «Darwin») ενώ ο κατάλογος εξόδου μονάδων του package όχι, οπότε τα
// δύο συμφωνούν μόνο μετά από δίπλωμα σε πεζά. Σε σύστημα αρχείων
// ευαίσθητο σε πεζά-κεφαλαία εκείνη η διαφορά είναι ολόκληρη η αναζήτηση
Result := LowerCase({$I %FPCTARGETCPU%} + '-' + {$I %FPCTARGETOS%});
{$ENDIF}
end;
Γιατί ένα κεφαλαίο γράμμα χάλασε ολόκληρη την αλυσίδα
Το macro του μεταγλωττιστή γράφει το λειτουργικό σύστημα στόχο με αρχικό κεφαλαίο: Win64, Linux, Darwin. Το package Lazarus γράφει την έξοδο μονάδων του σε κατάλογο που ονομάζεται από τη δική του μεταβλητή στόχου, που είναι με πεζά: win64, linux, darwin. Δυο γραφές του ίδιου πράγματος, και κανένας τρόπος να το προσέξει κανείς σε Windows, όπου το σύστημα αρχείων δεν τις διακρίνει
Σε Linux είναι δύο διαφορετικοί κατάλογοι. Μια ανάπτυξη που βάζει το shared object στο DLLs/x86_64-linux είναι αόρατη για loader που ψάχνει DLLs/x86_64-Linux, οπότε και τα τέσσερα ρητά βήματα της αλυσίδας χάνουν και ο κώδικας πέφτει στο να αφήσει τον loader πλατφόρμας να ψάξει. Μερικές φορές αυτό δουλεύει, αν η βιβλιοθήκη τυχαίνει εγκατεστημένη σε επίπεδο συστήματος, και μερικές φορές όχι, και είτε έτσι είτε αλλιώς το προσεκτικά τακτοποιημένο δέντρο ανάπτυξης δεν συνεισφέρει τίποτα. Η αστοχία δεν έχει μήνυμα σφάλματος επειδή τίποτα δεν απέτυχε: κάθε βήμα ανέφερε σωστά ότι το αρχείο δεν ήταν εκεί που κοιτάζει
Το πρόγραμμα ανίχνευσης, μεταγλωττισμένο και εκτελεσμένο
Αυτή η κατηγορία σφαλμάτων δεν βρίσκεται με ανάγνωση, και δεν βρίσκεται ούτε με μεταγλώττιση. Η συνήθης τεχνική για την επαλήθευση κλάδου πλατφόρμας που δεν μεταγλωττίζεται ποτέ στο μηχάνημα ανάπτυξης είναι να αντιγραφεί η μονάδα σε προσωρινό κατάλογο, να μετονομαστεί, να αντικατασταθεί η συνθήκη πλατφόρμας με σύμβολο που δεν ορίζεται ποτέ, και να μεταγλωττιστεί το αντίγραφο· αν μεταγλωττιστεί, ο σύνδεσμος uses και οι υπογραφές κλήσεων σε εκείνη τη διαδρομή είναι τουλάχιστον αυτοσυνεπείς. Αυτό δουλεύει καλά για μια αυτοτελή μονάδα
Εδώ δεν δουλεύει. Η κύρια μονάδα σύνδεσης είναι πολύ μεγάλη και τραβάει μέσα το LCL, οπότε δεν μπορεί απλώς να αντιγραφεί και να μεταγλωττιστεί με το σύμβολο Windows σβηστό. Έτσι αντ' αυτού οι λίγες συναρτήσεις που άγγιξε η αλλαγή μεταγράφηκαν αυτούσιες σε ένα μικρό αυτοτελές πρόγραμμα, και εκείνο το πρόγραμμα εκτελέστηκε. Εκτύπωσε x86_64-Win64, και η ασυμφωνία ήταν ορατή σε μία γραμμή εξόδου. Η μεταγλώττιση του ίδιου προγράμματος δεν θα σας έλεγε τίποτα, επειδή η συμβολοσειρά είναι απόλυτα έγκυρη· μόνο η τιμή της είναι λάθος
program ProbeSubDir;
{$MODE DELPHI}
uses
SysUtils;
begin
// Εκτύπωση, όχι ισχυρισμός. Το νόημα είναι να κοιτάξετε την τιμή στην
// οποία πραγματικά αναπτύσσεται ένα macro σε αυτό το toolchain
Writeln('raw: ', {$I %FPCTARGETCPU%}, '-', {$I %FPCTARGETOS%});
Writeln('folded: ', LowerCase({$I %FPCTARGETCPU%} + '-' +
{$I %FPCTARGETOS%}));
end.
Το γενικό μάθημα: όταν μια δια-πλατφορμική αλλαγή αφορά την τιμή κάτι και όχι τον τύπο του, η επαλήθευση μόνο με μεταγλώττιση δεν είναι επαλήθευση. Τυπώστε το. Το ευρύτερο σύνολο δια-μεταγλωττιστικών διαφορών μεταξύ Delphi και Free Pascal συγκεντρώνεται στο άρθρο για τις παγίδες δια-μεταγλωττιστών Delphi και FPC
Αφήστε την πλατφόρμα να εξηγήσει τις δικές της αστοχίες φόρτωσης
Ο κλάδος Windows του loader απαριθμεί με το χέρι τους λόγους που μπορεί να αποτύχει μια φόρτωση, επειδή οι χρήσιμες διακρίσεις εκεί, μια ασυμφωνία αρχιτεκτονικής, μια απόν transitive εξάρτηση, μια διαδρομή που δεν επιλύεται, αντιστοιχούν σε κωδικούς σφάλματος που αξίζει να ονομαστεί ο καθένας ξεχωριστά. Εκτός Windows η φορητή μονάδα loader επιστρέφει ήδη περιγραφική συμβολοσειρά που καλύπτει το ίδιο έδαφος, οπότε ο κλάδος μη-Windows τη χρησιμοποιεί ευθεία αντί να εξάγει ξανά κατηγορίες από αριθμό σφάλματος που σημαίνει διαφορετικά πράγματα σε διαφορετικά συστήματα
Η αντίσταση στον πειρασμό να κανονικοποιηθούν τα δύο σε ένα μήνυμα είναι σκόπιμη. Μια αστοχία φόρτωσης είναι πρόβλημα ανάπτυξης, και ο άνθρωπος που διαβάζει το μήνυμα χρειάζεται το δικό του λεξιλόγιο της πλατφόρμας για να το ψάξει
Μια σύγκρουση ονομάτων που κάνει αναδρομή
Ακόμα μια παγίδα, μικρή και κοφτερή. Η φορητή μονάδα loader εξάγει μια διαδικασία που λέγεται UnloadLibrary, και η μονάδα σύνδεσης έχει διαδικασία ίδιου ονόματος που κάνει τη δική της λογιστική πριν αφήσει το handle. Μέσα σε εκείνη τη διαδικασία, μια μη προσδιορισμένη κλήση UnloadLibrary επιλύεται σε εκείνη της τρέχουσας μονάδας, που καλεί τον εαυτό της. Η διόρθωση είναι να προσδιοριστεί η κλήση με το όνομα της μονάδας
Αυτό είναι το ίδιο σχήμα με τα προβλήματα απόκρυψης αναγνωστικών που δεσπόζουν γενικά στις μεταφορές Free Pascal: η μονάδα Windows εξάγει συναρτήσεις ελάχιστου και μέγιστου ακεραϊκού τύπου που σκιάζουν αυτές κινητής υποδιαστολής, και τύπο συγχρονισμού που σκιάζει την κλάση του ίδιου ονόματος, και σε κάθε περίπτωση η επίλυση εξαρτάται από τη σειρά του συνδέσμου uses. Ο προσδιορισμός του σημείου κλήσης είναι η διόρθωση που δεν εξαρτάται από κάποιον που θα διατηρήσει εκείνη τη σειρά αργότερα
Λίστα ελέγχου ανάπτυξης
Τρία πράγματα εξηγούν τις περισσότερες αστοχίες φόρτωσης μια που η αριθμητική των διαδρομών είναι σωστή. Η αρχιτεκτονική πρέπει να ταιριάζει με τη διεργασία, όχι με το μηχάνημα, οπότε μια εφαρμογή 32-bit σε 64-bit Windows χρειάζεται το δυαδικό αρχείο 32-bit. Το build με ενεργοποιημένο V8 έχει διαφορετικό όνομα αρχείου, οπότε μια ανάπτυξη που τα μπερδεύει θα φαίνεται σωστή και δεν θα φορτώσει τίποτα. Και μόνο μία παραλλαγή μπορεί να ζει σε κατάλογο συστήματος κάθε φορά, που είναι καλός λόγος προτίμησης της ρητής διάταξης υποκαταλόγου έναντι εγκατάστασης οτιδήποτε σε επίπεδο συστήματος
Για το Lazarus συγκεκριμένα, βάλτε την εγγενή βιβλιοθήκη κάτω από DLLs/<cpu>-<os> με πεζά, δίπλα στο εκτελέσιμο, και θα βρίσκεται από το πρώτο βήμα της αλυσίδας σε κάθε στόχο. Το δείγμα viewer που το ασκεί στο Lazarus περιγράφεται στο άρθρο για τον viewer Lazarus και FPC, και η τρέχουσα υποστήριξη πλατφορμών καταγράφεται στη σελίδα προϊόντος PDFium Delphi component