Odborný článok

Načítanie natívnej knižnice PDFium na ľubovoľnom cieli

Komponent PDFium nachádza svoju natívnu knižnicu cez pevný, usporiadaný reťaz hľadania namiesto toho, aby to nechal na loaderi operačného systému, pretože nasadzovací strom, ktorý je explicitný, je nasadzovací strom, ktorý môžete ladiť. Na Windows ten reťaz hľadá podadresár Win32 alebo Win64, ktorý už dodáva inštalátor. Na iných cieľoch stavia meno podadresára z makier cieľov Free Pascalu, ako <cpu>-<os>, takže nasadzovací strom sa číta presne ako strom skompilovaných jednotiek. Toto posledné rozhodnutie zaviedlo chybu, ktorá stojí za celý článok, pretože príčinou bolo veľké písmeno a príznakom bolo ticho

Reťaz, v poradí

Štyri miesta, skúšané postupne, potom platformový loader ako posledná možnosť. Najprv preferované rozloženie, adresár DLLs vedľa spustiteľného súboru obsahujúci jeden podadresár na cieľ. Po druhé alternatívne rozloženie s cieľovým podadresárom priamo vedľa spustiteľného súboru. Po tretie ploché legacy rozloženie, knižnica sediaca vedľa spustiteľného súboru bez akéhokoľvek podadresára. Po štvrté, len na Windows, systémový adresár, ktorý si vyžaduje starostlivosť, pretože 32-bitový proces musí hľadať v SysWOW64 a 64-bitový v System32 a na 32-bitovom Windows ten prvý neexistuje, takže dohľad sa musí vrátiť späť. Až po všetkom tom sa loadera pýta, aby hľadal sám

Diagram reťaze hľadania natívnej knižnice PDFium pre Delphi, od cieľového podadresára DLLs cez alternatívne, ploché a systémové rozloženia Windows až po platformový loader
Štyri explicitné miesta sa preskúmajú v poradí skôr, než sa loader OS poprosí, aby hľadal sám

Mimo Windows zámerne nie je krok so systémovým adresárom. Vlastná cesta hľadania platformového loadera, riadená konfiguráciou runtime linkera a prostredím cesty knižníc, pokrýva tú zem už sama a jej duplikovanie v Pascale by znamenalo reimplementovať pravidlá, ktoré sa líšia podľa distribúcie. Diagnostikovanie zlyhaní v reťazi Windows je pokryté osobitne v nasadzovaní DLL PDFium a diagnostike zlyhaní načítania

Odkiaľ pochádza meno podadresára

Na Windows je to Win32 alebo Win64, rozhodnuté bitovosťou bežiaceho procesu, nie operačného systému, pretože to je to, čo určuje, ktorý binárny súbor sa dá načítať. Všade inde sa meno stavia z makier cieľov kompilátora tak, že stroj zostavujúci pre dve architektúry produkuje dva zreteľne oddelené stromy a tak, že priečinok nesúci natívnu knižnicu sedí vedľa priečinka nesúceho skompilované jednotky pod tým istým menom

function BuildDllSubDir(UseV8: Boolean): string;
begin
{$IFDEF MSWINDOWS}
  if IsWin64 then
    Result := 'Win64'
  else
    Result := 'Win32';
{$ELSE}
  // Kompilátorové makrá píšu OS s počiatočným veľkým („Linux“, „Darwin“),
  // zatiaľ čo výstupný adresár jednotiek balíka nie, takže sa zhodujú až
  // po zložení. Na systéme súborov rozlišujúcom veľkosť je tento rozdiel
  // celý dohľad
  Result := LowerCase({$I %FPCTARGETCPU%} + '-' + {$I %FPCTARGETOS%});
{$ENDIF}
end;

Prečo jedno veľké písmeno rozbilo celý reťaz

Makro kompilátora hláskuje cieľový operačný systém s počiatočným veľkým písmenom: Win64, Linux, Darwin. Balík Lazarus zapisuje výstup jednotiek do adresára pomenovaného z vlastnej cieľovej premennej, ktorá je malými písmenami: win64, linux, darwin. Dva pravopisy tej istej veci a žiadny spôsob, ako si to všimnúť na Windows, kde systém súborov ich nerozlišuje

Na Linuxe sú to dva rozdielne adresáre. Nasadenie, ktoré dáva shared object do DLLs/x86_64-linux, je neviditeľné pre loader hľadajúci DLLs/x86_64-Linux, takže všetky štyri explicitné kroky reťaze minú a kód prepadne k tomu, že hľadať nechá platformový loader. Niekedy to funguje, ak sa knižnica náhodou inštalovala systémovo, a niekedy nie a buď tak či onak starostlivo usporiadaný nasadzovací strom neprispieva ničím. Zlyhanie nemá chybové hlásenie, pretože nič nezlyhalo: každý krok správne hlásil, že súbor nie je tam, kde hľadal

Ako jedno veľké písmeno v cieľovom makre FPC rozbije dohľad DLL PDFium na Linuxe: loader hľadá DLLs/x86_64-Linux, zatiaľ čo nasadený priečinok je DLLs/x86_64-linux, čo sedí len na Windows nerozlišujúcom veľkosť
Ten istý cieľ hláskovaný dvoma spôsobmi sedí na Windows a potichu minie na systéme súborov rozlišujúcom veľkosť

Probe program, skompilovaný a spustený

Túto triedu chýb nemožno nájsť čítaním a nemožno ju nájsť ani kompilovaním. Obvyklá technika na overenie platformovej vetvy, ktorá sa na vývojovom stroji nikdy nekompiluje, je skopírovať jednotku do dočasného adresára, premenovať ju, nahradiť platformovú podmienku symbolom, ktorý nie je nikdy definovaný, a skompilovať kópiu; ak sa skompiluje, uses klauzula a signatúry volaní na tej ceste sú aspoň vnútorne konzistentné. To funguje dobre pre sebestačnú jednotku

Tu to nefunguje. Hlavná viažuca jednotka je veľmi veľká a priťahuje LCL, takže ju nemožno jednoducho skopírovať a skompilovať so symbolom Windows vypnutým. Namiesto toho bola hrsť funkcií, ktorých sa zmena dotkla, prepísaná doslova do malého sebestačného programu a ten program bol spustený. Vypísal x86_64-Win64 a nesúlad bol viditeľný v jednom riadku výstupu. Skompilovanie toho istého programu by vám nepovedalo nič, pretože reťazec je úplne platný; zlá je len jeho hodnota

program ProbeSubDir;
{$MODE DELPHI}
uses
  SysUtils;
begin
  // Vypísať, netvrdiť. Zmysel je pozrieť sa na hodnotu, na ktorú sa makro
  // na tomto toolchaine naozaj rozvinie
  Writeln('raw:    ', {$I %FPCTARGETCPU%}, '-', {$I %FPCTARGETOS%});
  Writeln('folded: ', LowerCase({$I %FPCTARGETCPU%} + '-' +
    {$I %FPCTARGETOS%}));
end.

Všeobecná lekcia: keď sa zmena naprieč platformami týka hodnoty niečoho, nie jej typu, overenie len kompiláciou nie je overenie. Vypíšte to. Širšia sada rozdielov naprieč kompilátormi medzi Delphi a Free Pascalom je zozbieraná v článku o pasciach kompilátorov Delphi a FPC

Nechajte platformu vysvetliť vlastné zlyhania načítania

Vetva Windows loadera vyratúva rukou dôvody, pre ktoré môže načítanie zlyhať, pretože užitočné rozlíšenia tam — nesúlad architektúr, chýbajúca tranzitívna závislosť, cesta, ktorá sa nerozrieši — sa mapujú na chybové kódy stojace za individuálne pomenovanie. Mimo Windows vracia prenosná loaderová jednotka už popisný reťazec pokrývajúci tú istú zem, takže vetva non-Windows ho používa priamo namiesto znovu odvodzovania kategórií z chybového čísla, ktoré znamená odlišné veci na odlišných systémoch

Vzdorovanie pokušeniu normalizovať tie dva do jednej správy je zámerné. Zlyhanie načítania je problém nasadenia a človek čítajúci správu potrebuje vlastnú slovnú zásobu platformy, aby si ju vyhľadal

Kolízia mien, ktorá rekurzuje

Ešte jedna pasca, malá a ostrá. Prenosná loaderová jednotka exportuje procedúru volanú UnloadLibrary a viažuca jednotka má procedúru toho istého mena, ktorá si pred uvoľnením handlu vedie vlastnú evidenciu. Vnútri tej procedúry sa nekvalifikované volanie UnloadLibrary rozrieši na to v aktuálnej jednotke, ktorá volá samu seba. Opravou je kvalifikovať volanie menom jednotky

Toto je ten istý tvar ako problémy tienenia identifikátorov, ktoré všeobecne dominujú portom do Free Pascalu: jednotka Windows exportuje celočíselné funkcie minima a maxima, ktoré tienenia tie s pohyblivou rádovou čiarkou, a synchronizačný typ, ktorý tienenia triedu toho istého mena, a v každom prípade závisí rozriešenie od poradia uses klauzuly. Kvalifikovanie miesta volania je oprava, ktorá nezávisí od toho, že niekto toto poradie neskôr zachová

Kolízia mien UnloadLibrary v komponente PDFium: nekvalifikované volanie v viažucej jednotke Delphi rekurzuje do seba, zatiaľ čo jednotkou kvalifikované volanie dosiahne prenosnú loaderovú jednotku a uvoľní handle
Kvalifikovanie miesta volania posiela uvoľnenie cez loaderovú jednotku namiesto rekurzie do viažucej jednotky

Kontrolný zoznam nasadenia

Tri veci vysvetľujú väčšinu zlyhaní načítania, keď je aritmetika ciest správna. Architektúra sa musí zhodovať s procesom, nie so strojom, takže 32-bitová aplikácia na 64-bitovom Windows potrebuje 32-bitový binárny súbor. Build so zapnutým V8 má iné meno súboru, takže nasadenie, ktoré ich zmieša, bude vyzerať správne a nenačíta nič. A naraz môže bývať v systémovom adresári len jedna varianta, čo je dobrý dôvod preferovať explicitné rozloženie s podadresárom pred inštalovaním čohokoľvek systémovo

Špecificky pre Lazarus dajte natívnu knižnicu pod DLLs/<cpu>-<os> malými písmenami, vedľa spustiteľného súboru, a bude nájdená prvým krokom reťaze na každom cieli. Vzorka prehliadača, ktorá to na Lazaruse precvičuje, je popísaná v článku o prehliadači Lazarus a FPC a aktuálna podpora platforiem je uvedená na produktovej stránke PDFium Delphi component