PDFium-komponenten hittar sitt nativa bibliotek genom en fast, ordnad sökkedja i stället för att lämna det till operativsystemets laddare, för ett distributionsträd som är uttryckligt är ett distributionsträd du kan felsöka. På Windows söker den kedjan efter en Win32- eller Win64-underkatalog som installationsprogrammet redan levererar. På andra mål bygger den underkatalognamnet ur Free Pascal-målmakrona, som <cpu>-<os>, så att distributionsträdet läses exakt som trädet för kompilerade enheter. Det senaste beslutet introducerade en bugg som är värd hela artikeln, eftersom orsaken var en versal och symptomet var tystnad
Kedjan, i ordning
Fyra platser, prövade i sekvens, sedan plattformsladdaren som sista utväg. Först det föredragna upplägget, en DLLs-katalog bredvid den körbara filen som innehåller en underkatalog per mål. Sedan ett alternativt upplägg med målunderkatalogen direkt bredvid den körbara filen. Tredje det platta äldre upplägget, biblioteket liggande bredvid den körbara filen utan någon underkatalog alls. Fjärde, enbart på Windows, systemkatalogen, som kräver omsorg eftersom en 32-bitsprocess måste titta i SysWOW64 och en 64-bitsprocess i System32, och på 32-bits Windows finns den förra inte så uppslaget måste falla tillbaka. Först efter allt det blir laddaren ombedd att söka på egen hand
Det finns med flit inget systemkatalogsteg utanför Windows. Plattformsladdarens egen sökväg, driven av runtime-länkarkonfigurationen och biblioteksvägsmiljön, täcker redan den marken, och att duplicera den i Pascal skulle betyda att reimplementera regler som varierar per distribution. Att diagnostisera fel i Windows-kedjan tas upp separat i att distribuera PDFium-DLL:en och diagnostisera laddfel
Varifrån underkatalognamnet kommer
På Windows är det Win32 eller Win64, beslutat av processens bitbredd i stället för operativsystemets, eftersom det är det som avgör vilken binärfil som kan laddas. Överallt annars byggs namnet ur kompilatorns målmakron så att en maskin som bygger för två arkitekturer producerar två tydligt åtskilda träd, och så att mappen som håller det nativa biblioteket sitter bredvid mappen som håller de kompilerade enheterna under samma namn
function BuildDllSubDir(UseV8: Boolean): string;
begin
{$IFDEF MSWINDOWS}
if IsWin64 then
Result := 'Win64'
else
Result := 'Win32';
{$ELSE}
// Kompilatorns makron skriver OS med versal begynnelsebokstav
// ("Linux", "Darwin") medan paketets enhetsutdatakatalog inte gör
// det, så de två överensstämmer först efter att skiftläget vikits
// ihop. På ett skiftlägeskänsligt filsystem är den skillnaden
// hela uppslaget
Result := LowerCase({$I %FPCTARGETCPU%} + '-' + {$I %FPCTARGETOS%});
{$ENDIF}
end;
Varför en versal bröt hela kedjan
Kompilatorns makro stav måloperativsystemet med begynnande versal: Win64, Linux, Darwin. Lazarus-paketet skriver sitt enhetsutdata i en katalog namngiven ur sin egen målvariabel, som är gemen: win64, linux, darwin. Två stavningar av samma sak, och inget sätt att lägga märke till det på Windows, där filsystemet inte skiljer dem åt
På Linux är de två olika kataloger. En distribution som lägger det delade objektet i DLLs/x86_64-linux är osynlig för en laddare som letar efter DLLs/x86_64-Linux, så alla fyra uttryckliga steg i kedjan missar och koden faller igenom till att låta plattformsladdaren söka. Ibland fungerar det, om biblioteket råkar vara installerat systemövergripande, och ibland gör det inte det, och oavsett vilket bidrar det noggrant arrangerade distributionsträdet med ingenting. Felet har inget felmeddelande eftersom ingenting misslyckades: varje steg rapporterade korrekt att filen inte var där den letade
Sonderprogrammet, kompilerat och kört
Denna klass av buggar kan inte hittas genom att läsa, och den kan inte hittas genom att kompilera heller. Den vanliga tekniken för att verifiera en plattformsgren som aldrig kompilerar på utvecklingsmaskinen är att kopiera enheten till en tillfällig katalog, döpa om den, ersätta plattformsvillkoret med en symbol som aldrig definieras och kompilera kopian; kompilerar den är uses-satsen och anropssignaturerna på den vägen åtminstone självkonsistenta. Det fungerar bra för en självförsörjande enhet
Det fungerar inte här. Huvudbindningsenheten är mycket stor och drar in LCL, så den kan inte enkelt kopieras och kompileras med Windowssymbolen avstängd. I stället transkriberades de handfull funktioner ändringen rörde ordagrant in i ett litet självförsörjande program, och det programmet kördes. Det skrev ut x86_64-Win64, och avvikelsen var synlig på en rad utmatning. Att kompilera samma program skulle inte ha sagt dig någonting, eftersom strängen är fullständigt giltig; bara dess värde är fel
program ProbeSubDir;
{$MODE DELPHI}
uses
SysUtils;
begin
// Skriv ut, inte asserta. Poängen är att titta på värdet ett makro
// faktiskt expanderar till på denna verktygskedja
Writeln('raw: ', {$I %FPCTARGETCPU%}, '-', {$I %FPCTARGETOS%});
Writeln('folded: ', LowerCase({$I %FPCTARGETCPU%} + '-' +
{$I %FPCTARGETOS%}));
end.
Den allmänna läxan: när en korsplattformsändring gäller värdet av någonting snarare än dess typ är kompilera-enbart-verifiering inte verifiering. Skriv ut det. Den bredare uppsättningen skillnader mellan Delphi och Free Pascal samlas i artikeln om korskompilatorfallgropar för Delphi och FPC
Låt plattformen förklara sina egna laddfel
Windows-grenen av laddaren räknar upp för hand orsakerna ett laddfel kan ha, eftersom de användbara distinktionerna där, en arkitekturavvikelse, ett saknat transitivt beroende, en väg som inte löser upp, mappas till felkoder värda att namnge var för sig. Utanför Windows returnerar den portabla laddarenheten redan en beskrivande sträng som täcker samma mark, så icke-Windows-grenen använder den direkt i stället för att härleda kategorier om ur ett felnummer som betyder olika saker på olika system
Att stå emot frestelsen att normalisera de två till ett meddelande är medvetet. Ett laddfel är ett distributionsproblem, och personen som läser meddelandet behöver plattformens egen vokabulär för att söka efter det
En namnkollision som rekurrerar
En fälla till, liten och skarp. Den portabla laddarenheten exporterar en procedur som heter UnloadLibrary, och bindningsenheten har en procedur med samma namn som gör sin egen bokföring innan handle:et släpps. Inuti den proceduren löser ett okvalificerat anrop av UnloadLibrary upp sig till den i den aktuella enheten, som anropar sig själv. Fixen är att kvalificera anropet med enhetsnamnet
Det är samma form som de identifierarskuggningsproblem som dominerar Free Pascal-porteringar i allmänhet: Windows-enheten exporterar heltalstypade minimum- och maximumfunktioner som skuggar de flyttalsbaserade, och en synkroniseringstyp som skuggar klassen med samma namn, och i vart och ett fall beror upplösningen på ordningen i uses-satsen. Att kvalificera anropsplatsen är fixen som inte beror på att någon bevarar den ordningen senare
Distributionschecklista
Tre saker står för de flesta laddfelen när vägaritmetiken väl är rätt. Arkitekturen måste matcha processen, inte maskinen, så en 32-bitsapplikation på 64-bits Windows behöver 32-bitsbinärfilen. V8-aktiverade bygget har ett annat filnamn, så en distribution som blandar dem kommer se korrekt ut och ladda ingenting. Och bara en variant kan bo i en systemkatalog åt gången, vilket är en god anledning att föredra det uttryckliga underkatalogupplägget framför att installera någonting systemövergripande
För Lazarus specifikt, lägg det nativa biblioteket under DLLs/<cpu>-<os> med gemener, bredvid den körbara filen, så hittas det av första steget i kedjan på vart och ett mål. Visarexemplet som trär detta på Lazarus beskrivs i artikeln om Lazarus- och FPC-visaren, och aktuellt plattformsstöd listas på produktsidan för PDFium Delphi component