Tehnički članak

Učitavanje nativne PDFium biblioteke na bilo kom cilju

PDFium komponenta nalazi svoju nativnu biblioteku kroz fiksni, uređen lanac pretrage umesto da to prepusti učitaču operativnog sistema, jer je stablo raspoređivanja koje je eksplicitno stablo koje možete otklanjati. Na Windows-u taj lanac traži Win32 ili Win64 poddirektorijum koji instalater već isporučuje. Na drugim ciljevima gradi ime poddirektorijuma iz makroa Free Pascal cilja, kao <cpu>-<os>, pa se stablo raspoređivanja čita tačno kao stablo kompajliranih jedinica. Ta poslednja odluka unela je grešku vrednu celog članka, jer je uzrok bilo veliko slovo, a simptom tišina

Lanac, po redu

Četiri lokacije, pokušavane u nizu, pa učitač platforme kao poslednje utočište. Prvo željeni raspored, DLLs direktorijum pored izvršne datoteke koji sadrži po jedan poddirektorijum po cilju. Drugo alternativni raspored sa ciljnim poddirektorijumom pravo pored izvršne datoteke. Treće ravni nasleđeni raspored, biblioteka sedi pored izvršne datoteke bez ikakvog poddirektorijuma. Četvrto, samo na Windows-u, sistemski direktorijum, koji traži pažnju jer 32-bitni proces mora gledati u SysWOW64 a 64-bitni proces u System32, i na 32-bitnom Windows-u prvi ne postoji pa traženje mora pasti nazad. Tek posle svega toga učitač se traži da traži sam

Dijagram lanca pretrage nativne PDFium biblioteke za Delphi, od DLLs ciljnog poddirektorijuma kroz alternativne, ravne i Windows sistemske rasporede do učitača platforme
Četiri eksplicitne lokacije ispitivane su po redu pre nego što se OS učitač traži da traži sam

Nema namerno koraka sistemskog direktorijuma van Windows-a. Sopstvena putanja pretrage učitača platforme, vođena konfiguracijom runtime linkera i okruženjem putanje biblioteka, već pokriva to tlo, i dupliranje u Pascal-u značilo bi ponovno implementiranje pravila koja variraju po distribuciji. Dijagnostikovanje otkazivanja u Windows lancu pokriveno je odvojeno u raspoređivanju PDFium DLL-a i dijagnostikovanju otkazivanja učitavanja

Odakle ime poddirektorijuma dolazi

Na Windows-u to je Win32 ili Win64, odlučeno bitnošću procesa u izvršavanju umesto operativnog sistema, jer to određuje koja binarna datoteka može biti učitana. Svuda drugde ime se gradi iz makroa cilja kompajlera tako da mašina koja gradi za dve arhitekture proizvodi dva jasno odvojena stabla, i tako da fascikla koja drži nativnu biblioteku sedi pored fascikle koja drži kompajlirane jedinice sa istim imenom

function BuildDllSubDir(UseV8: Boolean): string;
begin
{$IFDEF MSWINDOWS}
  if IsWin64 then
    Result := 'Win64'
  else
    Result := 'Win32';
{$ELSE}
  // Makroi kompajlera pišu OS velikim početnim slovom ("Linux",
  // "Darwin") dok izlazni direktorijum jedinica paketa ne, pa se dva
  // slažu tek posle savijanja. Na sistemu datoteka osetljivom na
  // velika i mala slova ta razlika je celo traženje
  Result := LowerCase({$I %FPCTARGETCPU%} + '-' + {$I %FPCTARGETOS%});
{$ENDIF}
end;

Zašto je jedno veliko slovo slomilo ceo lanac

Makro kompajlera piše ciljni operativni sistem sa početnim velikim slovom: Win64, Linux, Darwin. Lazarus paket piše svoj izlaz jedinica u direktorijum imenovan iz sopstvene ciljne promenljive, koja je mala slova: win64, linux, darwin. Dva pravopisa iste stvari, i nema načina primetiti na Windows-u, gde sistem datoteka ne razlikuje

Na Linux-u oni su dva različita direktorijuma. Raspoređivanje koje stavi deljeni objekat u DLLs/x86_64-linux nevidljivo je učitaču koji traži DLLs/x86_64-Linux, pa sva četiri eksplicitna koraka lanca promaše i kod pada dalje na pustiti učitača platforme da traži. Ponekad to radi, ako se biblioteka slučajno instalirana sistemski, i ponekad ne, i svakako pažljivo uređeno stablo raspoređivanja ne doprinosi ništa. Otkazivanje nema poruku greške jer ništa nije otkazalo: svaki korak ispravno je izveštavao da datoteka nije tamo gde je gledao

Kako jedno veliko slovo u FPC makrou cilja lomi traženje PDFium DLL-a na Linux-u: učitač traži DLLs/x86_64-Linux dok je raspoređena fascikla DLLs/x86_64-linux, što se poklapa samo na Windows-u neosetljivom na velika i mala slova
Isti cilj napisan na dva načina poklapa se na Windows-u i tiho promašuje na sistemu datoteka osetljivom na velika i mala slova

Probni program, kompajliran i pokrenut

Ova klasa grešaka ne može se naći čitanjem, i ne može se naći ni kompajliranjem. Uobičajena tehnika za verifikaciju grane platforme koja se nikad ne kompajlira na razvojnoj mašini jeste kopirati jedinicu u privremeni direktorijum, preimenovati je, zameniti platformski uslovni simbolom koji nikad nije definisan, i kompajlirati kopiju; ako se kompajlira, uses klauza i potpisi poziva na toj putanji barem su samo-konzistentni. To dobro radi za samodovoljnu jedinicu

Ovde ne radi. Glavna jedinica vezivanja vrlo je velika i vuče LCL, pa se ne može jednostavno kopirati i kompajlirati sa Windows simbolom isključenim. Umesto toga, pregršt funkcija koje je izmena dirala prepisano je doslovno u mali samodovoljan program, i taj program je pokrenut. Ispisao je x86_64-Win64, i neslaganje bilo je vidljivo u jednoj liniji izlaza. Kompajliranje istog programa reklo bi vam ništa, jer je niz znakova savršeno valjan; samo je njegova vrednost pogrešna

program ProbeSubDir;
{$MODE DELPHI}
uses
  SysUtils;
begin
  // Ispišite, ne tvrdite. Poenta je pogledati vrednost koju makro
  // zapravo širi na ovom toolchain-u
  Writeln('raw:    ', {$I %FPCTARGETCPU%}, '-', {$I %FPCTARGETOS%});
  Writeln('folded: ', LowerCase({$I %FPCTARGETCPU%} + '-' +
    {$I %FPCTARGETOS%}));
end.

Opšta lekcija: kada se međuplatformska izmena tiče vrednosti nečega umesto njegovog tipa, verifikacija samo-kompajliranjem nije verifikacija. Ispišite je. Širi skup razlika među-kompajlera između Delphi-ja i Free Pascal-a sabran je u članku o zamkama Delphi i FPC među-kompajlera

Pustite platformu da objasni sopstvene neuspehe učitavanja

Windows grana učitača ručno nabraja razloge zbog kojih učitavanje može otkazati, jer se korisne razlike tamo, neslaganje arhitekture, nedostajuća tranzitivna zavisnost, putanja koja se ne razrešuje, mapiraju u kodove grešaka vredne imenovanja pojedinačno. Van Windows-a prenosiva jedinica učitača već vraća opisni niz znakova koji pokriva isto tlo, pa non-Windows grana koristi nju direktno umesto ponovnog izvođenja kategorija iz broja greške koji znači različite stvari na različitim sistemima

Otpor nagonu da se te dve normalizuju u jednu poruku namerni je. Neuspeh učitavanja problem je raspoređivanja, i osoba koja čita poruku traži sopstveni rečnik platforme da ga pronađe

Sudar imena koji se rekurzira

Još jedna zamka, mala i oštra. Prenosiva jedinica učitača izvozi proceduru zvanu UnloadLibrary, i jedinica vezivanja ima proceduru istog imena koja radi svoje knjigovodstvo pre oslobađanja handle-a. Unutar te procedure, nekvalifikovan poziv UnloadLibrary razrešava se u onu u tekućoj jedinici, koja zove samu sebe. Popravka je kvalifikovati poziv imenom jedinice

Ovo je isti oblik kao problemi zasenjivanja identifikatora koji dominiraju Free Pascal portovima opšte: Windows jedinica izvozi minimum i maksimum funkcije celobrojnog tipa koje zasenjuju verzije sa pokretnim zarezom, i tip sinhronizacije koji zasenjuje klasu istog imena, i u svakom slučaju razrešenje zavisi od redosleda uses klauze. Kvalifikovanje mesta poziva je popravka koja ne zavisi od nekoga ko čuva taj redosled kasnije

Sudar imena UnloadLibrary u PDFium komponenti: nekvalifikovan poziv u Delphi jedinici vezivanja rekurzira u samu sebe, dok jedinicom-kvalifikovan poziv stiže do prenosive jedinice učitača i oslobađa handle
Kvalifikovanje mesta poziva šalje oslobađanje kroz jedinicu učitača umesto rekurzije u jedinicu vezivanja

Popis provere raspoređivanja

Tri stvari objašnjavaju većinu neuspeha učitavanja jednom kada je aritmetika putanje ispravna. Arhitektura mora odgovarati procesu, ne mašini, pa 32-bitna aplikacija na 64-bitnom Windows-u traži 32-bitnu binarnu datoteku. Izgradnja sa omogućenim V8 ima drugo ime datoteke, pa raspoređivanje koje ih meša izgledaće ispravno i neće ništa učitati. I samo jedna varijanta može živeti u sistemskom direktorijumu istovremeno, što je dobar razlog da se preferira eksplicitni raspored poddirektorijuma nad instaliranjem bilo čega sistemski

Za Lazarus konkretno, stavite nativnu biblioteku pod DLLs/<cpu>-<os> malim slovima, pored izvršne datoteke, i naći će je prvi korak lanca na svakom cilju. Primer prikazivača koji ovo vežba na Lazarus-u opisan je u članku o Lazarus i FPC prikazivaču, a trenutna podrška platformi navedena je na stranici proizvoda PDFium Delphi component