Technický článek

Načítání nativní knihovny PDFium na jakémkoli cíli

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

Diagram řetězce hledání nativní knihovny PDFium pro Delphi od cílového podadresáře DLLs přes alternativní, ploché a systémové rozložení Windows po zavaděč platformy
Čtyři explicitní umístění se zkoumají v pořadí, než je zavaděč OS požádán, 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

Jak jedno velké písmeno v cílovém makru FPC rozbije hledání DLL PDFium na Linuxu: zavaděč hledá DLLs/x86_64-Linux, zatímco nasazená složka je DLLs/x86_64-linux, což sedí jen na Windows necitlivých na velikost písmen
Týž cíl napsaný dvěma způsoby sedí na Windows a potichu mine souborový systém citlivý na velikost písmen

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á

Kolize názvu UnloadLibrary v komponentě PDFium: nekvalifikované volání v Delphi vazební unitě se rekurzuje do sebe, zatímco unitou kvalifikované volání dosáhne přenositelné unity zavaděče a uvolní handle
Kvalifikace místa volání pošle uvolnění přes unitu zavaděče namísto rekurze do vazební unity

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