Componenta PDFium își găsește biblioteca nativă printr-un lanț de căutare fix și ordonat, în loc să lase sarcina loaderului sistemului de operare, deoarece un arbore de implementare care este explicit este un arbore de implementare pe care îl puteți depana. Pe Windows lanțul acela caută un subdirector Win32 sau Win64 pe care installerul îl livrează deja. Pe celelalte ținte construiește numele subdirectorului din macrocomenzile de țintă Free Pascal, ca <cpu>-<os>, astfel încât arborele de implementare se citește exact ca arborele de unități compilate. Ultima decizie a introdus o eroare care merită întregul articol, deoarece cauza a fost o literă mare, iar simptomul a fost tăcerea
Lanțul, în ordine
Patru locații, încercate în secvență, apoi loaderul platformei ca ultimă soluție. Mai întâi aspectul preferat, un director DLLs lângă executabil conținând câte un subdirector per țintă. Apoi un aspect alternativ cu subdirectorul de țintă direct lângă executabil. Apoi aspectul clasic plat, biblioteca așezată lângă executabil fără niciun subdirector. În al patrulea rând, doar pe Windows, directorul de sistem, care cere grijă, deoarece un proces pe 32 de biți trebuie să caute în SysWOW64, iar unul pe 64 de biți în System32, iar pe Windows pe 32 de biți primul nu există, deci căutarea trebuie să revină. Doar după toate acestea loaderul este rugat să caute singur
În mod deliberat nu există un pas de director de sistem în afara Windows. Calea de căutare proprie a loaderului platformei, condusă de configurația linkerului de runtime și de mediul de căi de biblioteci, acoperă deja acest teren, iar duplicarea ei în Pascal ar însemna re-implementarea unor reguli care variază după distribuție. Diagnosticarea eșecurilor din lanțul Windows este tratată separat în implementarea DLL-ului PDFium și diagnosticarea eșecurilor de încărcare
De unde vine numele subdirectorului
Pe Windows este Win32 sau Win64, decis de bitness-ul procesului în rulare, nu al sistemului de operare, deoarece asta determină ce binar poate fi încărcat. Oriunde altundeva numele este construit din macrocomenzile de țintă ale compilatorului, astfel încât o mașină care construiește pentru două arhitecturi produce doi arbori clar separați, iar dosarul care deține biblioteca nativă stă lângă dosarul care deține unitățile compilate cu același nume
function BuildDllSubDir(UseV8: Boolean): string;
begin
{$IFDEF MSWINDOWS}
if IsWin64 then
Result := 'Win64'
else
Result := 'Win32';
{$ELSE}
// Macrocomenzile compilatorului scriu OS-ul cu majusculă ("Linux", "Darwin"),
// în timp ce directorul de ieșire al unităților din pachet nu o face, deci
// cele două coincid doar după potrivirea cazului. Pe un sistem de fișiere
// sensibil la majuscule acea diferență este întreaga căutare
Result := LowerCase({$I %FPCTARGETCPU%} + '-' + {$I %FPCTARGETOS%});
{$ENDIF}
end;
De ce o literă mare a rupt tot lanțul
Macrocomanda compilatorului scrie sistemul de operare țintă cu majusculă inițială: Win64, Linux, Darwin. Pachetul Lazarus își scrie ieșirea de unități într-un director numit după propria lui variabilă de țintă, care este cu litere mici: win64, linux, darwin. Două scrieri ale aceluiași lucru, și niciun mod de a observa pe Windows, unde sistemul de fișiere nu le distinge
Pe Linux sunt două directoare diferite. O implementare care pune obiectul partajat în DLLs/x86_64-linux este invizibilă pentru un loader care caută DLLs/x86_64-Linux, deci toți cei patru pași expliciți ai lanțului ratează, iar codul cade în a lăsa loaderul platformei să caute. Uneori funcționează, dacă biblioteca se întâmplă să fie instalată la nivel de sistem, și uneori nu, și oricum arborele de implementare aranjat cu grijă nu contribuie cu nimic. Eșecul nu are niciun mesaj de eroare deoarece nimic nu a eșuat: fiecare pas a raportat corect că fișierul nu era unde a căutat
Programul de sondă, compilat și rulat
Această clasă de erori nu poate fi găsită citind, și nu poate fi găsită nici compilând. Tehnica obișnuită pentru a verifica o ramură de platformă care nu compilează niciodată pe mașina de dezvoltare este să copiați unitatea într-un director temporar, să o redenumiți, să înlocuiți condiționalul de platformă cu un simbol niciodată definit și să compilați copia; dacă compilează, clauza uses și semnăturile de apel de pe acea cale sunt măcar autoconsistente. Aceasta funcționează bine pentru o unitate autosuficientă
Aici nu funcționează. Unitatea principală de legare este foarte mare și trage LCL înăuntru, deci nu poate fi pur și simplu copiată și compilată cu simbolul Windows dezactivat. Deci, în schimb, cele câteva funcții atinse de schimbare au fost transcrise întocmai într-un mic program autosuficient, iar acel program a fost rulat. A tipărit x86_64-Win64, iar nepotrivirea era vizibilă într-o linie de rezultat. Compilarea aceluiași program nu v-ar fi spus nimic, deoarece șirul este perfect valid; doar valoarea lui este greșită
program ProbeSubDir;
{$MODE DELPHI}
uses
SysUtils;
begin
// Tipăriți, nu asertați. Ideea este să priviți valoarea la care o
// macrocomandă se extinde efectiv pe acest toolchain
Writeln('raw: ', {$I %FPCTARGETCPU%}, '-', {$I %FPCTARGETOS%});
Writeln('folded: ', LowerCase({$I %FPCTARGETCPU%} + '-' +
{$I %FPCTARGETOS%}));
end.
Lecția generală: când o schimbare cross-platform privește valoarea unui lucru, nu tipul lui, verificarea doar prin compilare nu este verificare. Tipăriți-o. Setul mai larg de diferențe între compilatoarele Delphi și Free Pascal este adunat în articolul despre capcanele cross-compiler Delphi și FPC
Lăsați platforma să își explice propriile eșecuri de încărcare
Ramura Windows a loaderului enumeră manual motivele pentru care o încărcare poate eșua, deoarece distincțiile utile acolo — o nepotrivire de arhitectură, o dependență tranzitivă lipsă, o cale care nu se rezolvă — se cartografiază la coduri de eroare care merită numite individual. În afara Windows unitatea portabilă de loader returnează deja un șir descriptiv care acoperă același teren, deci ramura non-Windows îl folosește direct în loc să re-deriveze categorii dintr-un număr de eroare care înseamnă lucruri diferite pe sisteme diferite
Rezistența la îndemnul de a normaliza acele două într-un singur mesaj este deliberată. Un eșec de încărcare este o problemă de implementare, iar persoana care citește mesajul are nevoie de vocabularul propriu al platformei pentru a-l căuta
O coliziune de nume care recursionează
Încă o capcană, mică și ascuțită. Unitatea portabilă de loader exportă o procedură numită UnloadLibrary, iar unitatea de legare are o procedură cu același nume care face propria ei contabilitate înainte să elibereze handle-ul. În interiorul acelei proceduri, un apel necalificat la UnloadLibrary se rezolvă la cea din unitatea curentă, care se apelează pe sine. Remedierea este să calificați apelul cu numele unității
Aceasta este aceeași formă ca problemele de umbrire de identificatori care domină în general portările Free Pascal: unitatea Windows exportă funcții de minim și maxim tip întreg care umbresc cele în virgulă mobilă, și un tip de sincronizare care umbrește clasa cu același nume, iar în fiecare caz rezolvarea depinde de ordinea clauzei uses. Calificarea locului de apel este remedierea care nu depinde de cineva care păstrează acea ordine mai târziu
Listă de verificare pentru implementare
Trei lucruri explică majoritatea eșecurilor de încărcare odată ce aritmetica căilor este corectă. Arhitectura trebuie să se potrivească procesului, nu mașinii, deci o aplicație pe 32 de biți pe Windows pe 64 de biți are nevoie de binarul pe 32 de biți. Build-ul activat cu V8 are un nume de fișier diferit, deci o implementare care le amestecă va părea corectă și nu va încărca nimic. Și doar o variantă poate trăi într-un director de sistem la un moment dat, ceea ce este un bun motiv de a prefera aspectul explicit cu subdirectoare în locul instalării oricărui lucru la nivel de sistem
Pentru Lazarus în special, puneți biblioteca nativă sub DLLs/<cpu>-<os> cu litere mici, lângă executabil, și va fi găsită de primul pas al lanțului pe fiecare țintă. Exemplul de vizualizator care exercită asta pe Lazarus este descris în articolul despre vizualizatorul Lazarus și FPC, iar suportul actual de platformă este listat pe pagina de produs PDFium Delphi component