PDFium-komponenten finder sit native-bibliotek gennem en fast, ordnet søgekæde i stedet for at overlade det til operativsystemets loader, for et udrulningstræ, der er eksplicit, er et udrulningstræ, du kan fejlfinde. På Windows kigger den kæde efter en Win32- eller Win64-undermappe, som installereren allerede udsender. På andre mål bygger den undermappens navn ud fra Free Pascal target-makroerne, som <cpu>-<os>, så udrulningstræet læses præcis som det kompilerede unit-træ. Den sidste beslutning indførte en fejl, der er hele artiklen værd, for årsagen var et versal, og symptomet var stilhed
Kæden, i rækkefølge
Fire placeringer, prøvet i sekvens, derefter platformloaderen som sidste udvej. Først det foretrukne layout, en DLLs-mappe ved siden af den eksekverbare med én undermappe pr. mål. Dernæst et alternativt layout med mål-undermappen direkte ved siden af den eksekverbare. Tredje det flade legacy-layout, biblioteket liggende ved siden af den eksekverbare uden nogen undermappe. Fjerde, kun på Windows, systemmappen, som behøver omhu, for en 32-bit-proces må kigge i SysWOW64 og en 64-bit-proces i System32, og på 32-bit Windows findes den førstnævnte ikke, så opslaget må falde tilbage. Først efter alt det bliver loaderen bedt om at søge på egen hånd
Der er bevidst intet systemmappe-trin uden for Windows. Platformloaderens egen søgesti, drevet af runtime linker-konfigurationen og biblioteksstiens miljø, dækker allerede det område, og at duplikere det i Pascal ville betyde at re-implementere regler, der varierer efter distribution. Fejlfinding i Windows-kæden er dækket separat i udrulning af PDFium DLL og diagnosticering af indlæsningsfejl
Hvor undermappens navn kommer fra
På Windows er det Win32 eller Win64, afgjort af den kørende proces' bitvidde snarere end operativsystemets, for det er dét, der afgør, hvilken binær der kan indlæses. Alle andre steder bygges navnet ud fra compilerens target-makroer, så en maskine, der bygger til to arkitekturer, producerer to klart adskilte træer, og så mappen, der rummer det native bibliotek, ligger ved siden af mappen, der rummer de kompilerede units, med samme navn
function BuildDllSubDir(UseV8: Boolean): string;
begin
{$IFDEF MSWINDOWS}
if IsWin64 then
Result := 'Win64'
else
Result := 'Win32';
{$ELSE}
// Compilermakroerne versaler OS'et ("Linux", "Darwin"), mens package
// unit-outputmappen ikke gør, så de to er enige først efter folding.
// På et filsystem, der skelner store og små bogstaver, er den forskel
// hele opslaget
Result := LowerCase({$I %FPCTARGETCPU%} + '-' + {$I %FPCTARGETOS%});
{$ENDIF}
end;
Hvorfor ét versal knækkede hele kæden
Compilermakroen staver mål-operativsystemet med begyndende versal: Win64, Linux, Darwin. Lazarus-pakken skriver sit unit-output ind i en mappe opkaldt efter sin egen target-variabel, som er med små bogstaver: win64, linux, darwin. To stavemåder af det samme, og ingen måde at lægge mærke til det på Windows, hvor filsystemet ikke skelner dem
På Linux er de to forskellige mapper. En udrulning, der lægger shared object i DLLs/x86_64-linux, er usynlig for en loader, der leder efter DLLs/x86_64-Linux, så alle kædens fire eksplicitte trin misser, og koden falder igennem til at lade platformloaderen søge. Nogle gange virker det, hvis biblioteket tilfældigvis er installeret systembredt, og nogle gange gør det ikke, og uanset hvad bidrager det omhyggeligt arrangerede udrulningstræ med ingenting. Fejlen har ingen fejlmeddelelse, for intet fejlede: hvert trin rapporterede korrekt, at filen ikke var der, hvor den kiggede
Probe-programmet, kompileret og kørt
Denne klasse af fejl kan ikke findes ved at læse, og den kan heller ikke findes ved at kompilere. Den sædvanlige teknik til at verificere en platformsgren, der aldrig kompilerer på udviklingsmaskinen, er at kopiere uniten til en midlertidig mappe, omdøbe den, erstatte den betingede platformskompilering med et symbol, der aldrig defineres, og kompilere kopien; kompilerer den, er uses-klausulen og kaldsignaturerne på den vej i det mindste selvkonsistente. Det virker fint for en selvkørende unit
Det virker ikke her. Hovedbindingsuniten er meget stor og trækker LCL ind, så den kan ikke blot kopieres og kompileres med Windows-symbolet slået fra. Så i stedet transskriberedes den håndfuld funktioner, ændringen rørte, ordret ind i et lille selvkørende program, og det program blev kørt. Det udskrev x86_64-Win64, og uoverensstemmelsen var synlig i én outputlinje. At kompilere samme program ville ikke have fortalt dig noget, for strengen er fuldstændig gyldig; kun dens værdi er forkert
program ProbeSubDir;
{$MODE DELPHI}
uses
SysUtils;
begin
// Udskriv, assert ikke. Pointen er at se på den værdi, en makro
// reelt ekspanderer til på denne toolchain
Writeln('raw: ', {$I %FPCTARGETCPU%}, '-', {$I %FPCTARGETOS%});
Writeln('folded: ', LowerCase({$I %FPCTARGETCPU%} + '-' +
{$I %FPCTARGETOS%}));
end.
Den generelle lektie: når en cross-platform ændring angår en værdi snarere end en type, er compile-only-verifikation ikke verifikation. Udskriv den. Det bredere sæt af cross-compiler-forskelle mellem Delphi og Free Pascal er samlet i artiklen om Delphi- og FPC cross-compiler-fælder
Lad platformen forklare sine egne indlæsningsfejl
Loaderens Windows-gren enumeratorer i hånden de grunde, en indlæsning kan fejle, for de nyttige skel dér — et arkitekturmismatch, en manglende transitiv afhængighed, en sti, der ikke opløses — mapper til fejlkoder værd at navngive hver for sig. Uden for Windows returnerer den portable loader-unit allerede en beskrivende streng, der dækker samme område, så ikke-Windows-grenen bruger den direkte frem for at genaflede kategorier fra et fejlnummer, der betyder forskellige ting på forskellige systemer
At modstå trangen til at normalisere de to til én meddelelse er bevidst. En indlæsningsfejl er et udrulningsproblem, og personen, der læser meddelelsen, behøver platformens eget ordforråd for at søge efter den
En navnekollision, der rekurserer
Endnu en fælde, lille og skarp. Den portable loader-unit eksporterer en procedure kaldet UnloadLibrary, og bindingsuniten har en procedure af samme navn, der foretager sin egen bogføring, før handleen frigives. Inden i den procedure opløses et ukvalificeret kald af UnloadLibrary til den i den aktuelle unit, som kalder sig selv. Rettelsen er at kvalificere kaldet med unitnavnet
Dette er samme form som de identifikator-skyggeproblemer, der dominerer Free Pascal-porteringer generelt: Windows-uniten eksporterer integer-typede minimums- og maksimumfunktioner, der skygger de flydende punkt-versioner, og en synkroniseringstype, der skygger klassen af samme navn, og i alle tilfælde afhænger opløsningen af rækkefølgen af uses-klausulen. At kvalificere kaldstedet er rettelsen, der ikke afhænger af, at nogen bevarer den rækkefølge senere
Udrulningstjekliste
Tre ting står for de fleste indlæsningsfejl, når stiaritmetikken først er rigtig. Arkitekturen skal matche processen, ikke maskinen, så en 32-bit-applikation på 64-bit Windows behøver den 32-bit binære. V8-aktiveret build har et andet filnavn, så en udrulning, der blander dem, vil se korrekt ud og indlæse intet. Og kun én variant kan bo i en systemmappe ad gangen, hvilket er en god grund til at foretrække det eksplicitte undermappe-layout frem for at installere noget systembredt
For Lazarus specifikt: læg det native bibliotek under DLLs/<cpu>-<os> med små bogstaver, ved siden af den eksekverbare, så findes det af kædens første trin på ethvert mål. Viewer-eksemplet, der afprøver dette på Lazarus, er beskrevet i artiklen om Lazarus- og FPC-vieweren, og aktuel platformunderstøttelse er opført på produktsiden PDFium Delphi component