Komponenta PDFium nalézá svou nativní knihovnu pevným, uspořádaným řetězcem hledání, nikoli odkládáním na zavaděč operačního systému, protože nasazovací strom, který je explicitní, je strom, který můžete ladit. Na Windows ten řetězec hledá podadresář Win32 nebo Win64, který už dodává instalátor. Na jiných cílech staví název podadresáře z cílových maker Free Pascalu jako <cpu>-<os>, takže nasazovací strom čte přesně jako strom kompilovaných unit. To poslední rozhodnutí přineslo chybu, která stojí za celý článek, protože příčinou bylo velké písmeno a symptomem bylo ticho
Řetězec, v pořadí
Čtyři umístění, zkoušená v sekvenci, pak zavaděč platformy jako poslední záchrana. Nejprve preferované rozložení, adresář DLLs vedle spustitelného souboru obsahující jeden podadresář na cíl. Za druhé alternativní rozložení s cílovým podadresářem přímo vedle spustitelného souboru. Za třetí ploché legacy rozložení, knihovna ležící vedle spustitelného souboru bez jakéhokoli podadresáře. Za čtvrté, jen na Windows, systémový adresář, který vyžaduje péči, protože 32bitový proces se musí dívat do SysWOW64 a 64bitový do System32 a na 32bitových Windows ta první neexistuje, takže hledání musí spadnout zpět. Teprve po všem tom se zavaděč požádá, aby hledal sám
Mimo Windows záměrně žádný krok systémového adresáře není. Vlastní hledací cesta zavaděče platformy, řízená konfigurací runtime linkeru a prostředím knihovních cest, toto území pokrývá už sama a duplikovat ji v Pascalu by znamenalo reimplementovat pravidla, která se liší podle distribuce. Diagnostika selhání v řetězci Windows je zvlášť předmětem nasazování DLL PDFium a diagnostiky selhání načítání
Odkud se bere název podadresáře
Na Windows je to Win32 nebo Win64, rozhodnuto bitovou šířkou běžícího procesu, nikoli operačního systému, protože právě ta určuje, který binární soubor lze načíst. Všude jinde se název staví z cílových maker kompilátoru tak, aby stroj stavějící pro dvě architektury produkoval dva zřetelně oddělené stromy a aby složka držící nativní knihovnu seděla vedle složky držící kompilované unity se stejným názvem
function BuildDllSubDir(UseV8: Boolean): string;
begin
{$IFDEF MSWINDOWS}
if IsWin64 then
Result := 'Win64'
else
Result := 'Win32';
{$ELSE}
// Makra kompilátoru dávají OS velké počáteční písmeno ("Linux", "Darwin"),
// zatímco výstupní adresář unit balíčku ne, takže obě souhlasí až po
// zkresování (folding). Na souborovém systému citlivém na velikost písmen
// je tento rozdíl celé hledání
Result := LowerCase({$I %FPCTARGETCPU%} + '-' + {$I %FPCTARGETOS%});
{$ENDIF}
end;
Proč jedno velké písmeno rozbilo celý řetězec
Makro kompilátoru píše cílový operační systém s počátečním velkým písmenem: Win64, Linux, Darwin. Balíček Lazarus zapisuje výstup unit do adresáře pojmenovaného z vlastní cílové proměnné, která je malými písmeny: win64, linux, darwin. Dva zápisy téže věci a žádný způsob, jak si jich všimnout na Windows, kde je souborový systém nerozlišuje
Na Linuxu jsou to dva různé adresáře. Nasazení, které dává shared object do DLLs/x86_64-linux, je neviditelné pro zavaděč hledající DLLs/x86_64-Linux, takže všechny čtyři explicitní kroky řetězce minou a kód spadne k tomu, aby nechal hledat zavaděč platformy. Někdy to funguje, pokud je knihovna náhodou nainstalována systémově, a někdy ne, a tak či onak pečlivě uspořádaný nasazovací strom nepřispěje ničím. Selhání nemá žádnou chybovou zprávu, protože nic neselhalo: každý krok správně nahlásil, že soubor není tam, kde se díval
Sondní program, zkompilovaný a spuštěný
Tuto třídu chyb nelze nalézt čtením a nelze ji nalézt ani kompilací. Běžnou technikou ověřování platformové větve, která se na vývojovém stroji nikdy nekompiluje, je zkopírovat unitu do dočasného adresáře, přejmenovat ji, nahradit platformovou podmínku symbolem, který nikdy není definován, a zkompilovat kopii; zkompiluje-li se, jsou uses klauzule a signatury volání na té cestě alespoň soběstačně konzistentní. To funguje dobře pro soběstačnou unitu
Zde to nefunguje. Hlavní vazební unita je velmi velká a táhne LCL, takže ji nelze prostě zkopírovat a zkompilovat s vypnutým symbolem Windows. Namísto toho byla hrstka funkcí, které změna zasáhla, přepsána slovo od slova do malého soběstačného programu a ten program byl spuštěn. Vytiskl x86_64-Win64 a neshoda byla viditelná na jednom řádku výstupu. Kompilace téhož programu by neřekla nic, protože řetězec je zcela platný; chybná je jen jeho hodnota
program ProbeSubDir;
{$MODE DELPHI}
uses
SysUtils;
begin
// Vytiskněte, ne assertujte. Smyslem je podívat se na hodnotu,
// na niž se makro na tomto toolchainu skutečně rozvine
Writeln('raw: ', {$I %FPCTARGETCPU%}, '-', {$I %FPCTARGETOS%});
Writeln('folded: ', LowerCase({$I %FPCTARGETCPU%} + '-' +
{$I %FPCTARGETOS%}));
end.
Obecná lekce: když se cross-platform změna týká hodnoty něčeho, nikoli jeho typu, ověření jen kompilací není ověření. Vytiskněte ji. Širší soubor rozdílů cross-kompilátorů mezi Delphi a Free Pascalem je shromážděn v článku o pastech cross-kompilace Delphi a FPC
Dejte platformě vysvětlit vlastní selhání načítání
Větev zavaděče pro Windows vyjmenovává ručně důvody, proč načtení může selhat, protože užitečná rozlišení tam — neshoda architektur, chybějící tranzitivní závislost, cesta, která se nerozřeší — se mapují na chybové kódy hodné pojmenování jednotlivě. Mimo Windows už přenositelná unita zavaděče vrací popisný řetězec pokrývající týž terén, takže větev mimo Windows jej používá přímo, namísto znovu odvozovat kategorie z chybového čísla, které znamená různé věci na různých systémech
Odolání pokušení normalizovat ty dva do jedné zprávy je záměrné. Selhání načítání je problém nasazení a člověk čtoucí zprávu potřebuje vlastní slovní zásobu platformy, aby ji mohl vyhledat
Kolize názvů, která se rekurzuje
Ještě jedna past, malá a ostrá. Přenositelná unita zavaděče exportuje proceduru zvanou UnloadLibrary a vazební unita má proceduru téhož názvu, která si dělá vlastní evidenci před uvolněním handle. Uvnitř té procedury se nekvalifikované volání UnloadLibrary rozřeší na tu v aktuální unitě, která volá sama sebe. Opravou je kvalifikovat volání názvem unity
Je to týž tvar jako problémy zastínění identifikátorů, které dominují portům Free Pascalu obecně: unita Windows exportuje celočíselné funkce minima a maxima, které zastíní ty s plovoucí desetinnou čárkou, a typ synchronizace zastínící třídu téhož názvu, a v každém případě závisí rozřešení na pořadí klauzule uses. Kvalifikace místa volání je oprava, která nezávisí na tom, že někdy později někdo toto pořadí zachová
Kontrolní seznam nasazení
Tři věci vysvětlují většinu selhání načítání, jakmile je aritmetika cest správná. Architektura se musí shodovat s procesem, nikoli se strojem, takže 32bitová aplikace na 64bitových Windows potřebuje 32bitový binární soubor. Sestavení s V8 má jiný název souboru, takže nasazení, které je promíchá, bude vypadat správně a nenačte nic. A jen jedna varianta může v systémovém adresáři žít najednou, což je dobrý důvod dát přednost explicitnímu rozložení podadresářů před instalací čehokoli systémově
Pro Lazarus zvlášť dejte nativní knihovnu pod DLLs/<cpu>-<os> malými písmeny vedle spustitelného souboru a bude nalezena prvním krokem řetězce na každém cíli. Ukázka prohlížeče, která to na Lazarusu procvičuje, je popsána v článku o prohlížeči Lazarus a FPC a aktuální podpora platforem je uvedena na stránce produktu PDFium Delphi component