De PDFium-component vindt zijn native bibliotheek via een vaste, geordende zoekketen in plaats van die aan de loader van het besturingssysteem over te laten, want een uitrolboom die expliciet is, is een uitrolboom die u kunt debuggen. Onder Windows zoekt die keten naar een submap Win32 of Win64 die het installatieprogramma al meebracht. Op andere doelen bouwt die de submapnaam uit de Free Pascal-doelmacro's, als <cpu>-<os>, zodat de uitrolboom er exact uitziet als de boom van gecompileerde units. Die laatste beslissing introduceerde een bug die het hele artikel waard is, want de oorzaak was een hoofdletter en het symptoom was stilte
De keten, in volgorde
Vier locaties, achtereenvolgens geprobeerd, daarna de platformloader als laatste redmiddel. Eerst de voorkeurslay-out, een map DLLs naast het uitvoerbare bestand met één submap per doel. Ten tweede een alternatieve lay-out met de doelsubmap rechtstreeks naast het uitvoerbare bestand. Ten derde de vlakke legacy-lay-out, de bibliotheek naast het uitvoerbare bestand zonder enige submap. Ten vierde, alleen onder Windows, de systeemmap, die zorg vraagt omdat een 32-bit-proces in SysWOW64 moet kijken en een 64-bit-proces in System32, en op 32-bit Windows bestaat de eerste niet, dus moet de zoekactie terugvallen. Pas na dat alles wordt de loader gevraagd zelf te zoeken
Buiten Windows is er bewust geen systeemmapstap. Het eigen zoekpad van de platformloader, gestuurd door de runtime-linkerconfiguratie en de bibliotheekpadomgeving, dekt dat terrein al, en het dupliceren in Pascal zou betekenen dat regels die per distributie verschillen opnieuw worden geïmplementeerd. Het diagnosticeren van storingen in de Windows-keten wordt apart behandeld in de PDFium DLL uitrollen en lafstoringen diagnosticeren
Waar de submapnaam vandaan komt
Onder Windows is het Win32 of Win64, bepaald door de bitbreedte van het draaiende proces in plaats van die van het besturingssysteem, want dat bepaalt welke binary kan worden geladen. Overal elders wordt de naam gebouwd uit de doelmacro's van de compiler zodat een machine die voor twee architecturen bouwt twee duidelijk gescheiden bomen oplevert, en zodat de map met de native bibliotheek naast de map met de gecompileerde units staat onder dezelfde naam
function BuildDllSubDir(UseV8: Boolean): string;
begin
{$IFDEF MSWINDOWS}
if IsWin64 then
Result := 'Win64'
else
Result := 'Win32';
{$ELSE}
// De compilermacro's zetten de OS met een hoofdletter ("Linux", "Darwin") terwijl
// de unit-uitvoermap van het pakket dat niet doet, dus de twee komen pas na
// folden overeen. Op een hoofdlettergevoelig bestandssysteem is dat verschil
// de hele zoekactie
Result := LowerCase({$I %FPCTARGETCPU%} + '-' + {$I %FPCTARGETOS%});
{$ENDIF}
end;
Waarom één hoofdletter de hele keten brak
De compilermacro schrijft het doelbesturingssysteem met een beginhoofdletter: Win64, Linux, Darwin. Het Lazarus-pakket schrijft zijn unituitvoer weg in een map die genoemd is naar zijn eigen doelvariabele, die kleine letters heeft: win64, linux, darwin. Twee spellingen van hetzelfde, en geen manier om het onder Windows op te merken, waar het bestandssysteem ze niet onderscheidt
Onder Linux zijn het twee verschillende mappen. Een uitrol die het shared object in DLLs/x86_64-linux zet is onzichtbaar voor een loader die naar DLLs/x86_64-Linux zoekt, dus alle vier de expliciete stappen van de keten missen en de code valt door naar het laten zoeken door de platformloader. Soms werkt dat, als de bibliotheek toevallig systeembreed is geïnstalleerd, en soms niet, en in beide gevallen draagt de zorgvuldig opgezette uitrolboom niets bij. De fout heeft geen foutmelding omdat niets faalde: elke stap meldde correct dat het bestand niet lag waar die keek
Het probe-programma, gecompileerd en uitgevoerd
Deze klasse bugs is niet te vinden door te lezen, en ook niet door te compileren. De gebruikelijke techniek om een platformtak te verifiëren die op de ontwikkelmachine nooit compileert, is de unit naar een tijdelijke map kopiëren, hernoemen, de platformconditional vervangen door een symbool dat nooit gedefinieerd is, en de kopie compileren; compileert die, dan zijn de uses-clausule en de aanroepsignaturen op dat pad in elk geval zelfconsistent. Dat werkt goed voor een zelfstandige unit
Hier werkt dat niet. De hoofdbindingsunit is zeer groot en trekt de LCL mee, dus die kan niet simpelweg worden gekopieerd en gecompileerd met het Windows-symbool uitgeschakeld. In plaats daarvan zijn de handjevol functies die de wijziging raakte woord voor woord getranscribeerd naar een klein zelfstandig programma, en dat programma werd uitgevoerd. Het printte x86_64-Win64, en de mismatch was zichtbaar in één regel uitvoer. Hetzelfde programma compileren had u niets verteld, want de string is volkomen geldig; alleen zijn waarde is fout
program ProbeSubDir;
{$MODE DELPHI}
uses
SysUtils;
begin
// Print, assert niet. Het punt is te kijken naar de waarde waartoe een macro
// op deze toolchain werkelijk uitvouwt
Writeln('raw: ', {$I %FPCTARGETCPU%}, '-', {$I %FPCTARGETOS%});
Writeln('folded: ', LowerCase({$I %FPCTARGETCPU%} + '-' +
{$I %FPCTARGETOS%}));
end.
De algemene les: wanneer een cross-platform-wijziging over de waarde van iets gaat in plaats van over zijn type, is alleen-compileren geen verificatie. Print het. De bredere verzameling cross-compiler-verschillen tussen Delphi en Free Pascal is gebundeld in het artikel over cross-compiler-valkuilen in Delphi en FPC
Laat het platform zijn eigen lafstoringen uitleggen
De Windows-tak van de loader somt met de hand de redenen op waarop een lading kan falen, omdat de nuttige onderscheidingen daar, een architectuurmismatch, een ontbrekende transitieve afhankelijkheid, een pad dat niet resolveert, mapten op foutcodes die het benoemen waard zijn. Buiten Windows geeft de portable loader-unit al een omschrijvende string terug die hetzelfde dekt, dus de niet-Windows-tak gebruikt die rechtstreeks in plaats van categorieën opnieuw af te leiden uit een foutnummer dat op verschillende systemen iets anders betekent
De druk weerstaan om die twee tot één bericht te normaliseren is bewust. Een lafstoring is een uitrolprobleem, en degene die het bericht leest heeft de eigen woordenschat van het platform nodig om ernaar te zoeken
Een naamgevingsbotsing die recursief wordt
Nog een valkuil, klein en scherp. De portable loader-unit exporteert een procedure genaamd UnloadLibrary, en de bindingsunit heeft een procedure met dezelfde naam die zijn eigen administratie doet voordat de handle wordt vrijgegeven. Binnen die procedure resolveert een niet-gekwalificeerde aanroep van UnloadLibrary naar die in de huidige unit, die zichzelf aanroept. De oplossing is de aanroep met de unitnaam te kwalificeren
Dit is dezelfde vorm als de identifier-schaduwproblemen die Free Pascal-porteringen over het algemeen beheersen: de Windows-unit exporteert integer-getypeerde minimum- en maximumfuncties die de floating-point-varianten beschaduwen, en een synchronisatietype die de klasse met dezelfde naam beschaduwt, en in elk geval hangt de resolutie af van de volgorde van de uses-clausule. De aanroepplaats kwalificeren is de oplossing die er niet van afhangt dat iemand die volgorde later bewaart
Uitrolchecklist
Drie dingen verklaren de meeste lafstoringen zodra de padberekening klopt. De architectuur moet bij het proces passen, niet bij de machine, dus een 32-bit-applicatie op 64-bit Windows heeft de 32-bit binary nodig. De build met V8 heeft een andere bestandsnaam, dus een uitrol die ze mengt ziet er correct uit en laadt niets. En slechts één variant kan tegelijk in een systeemmap staan, wat een goede reden is om de expliciete submaplay-out te verkiezen boven alles systeembreed installeren
Voor Lazarus specifiek: zet de native bibliotheek onder DLLs/<cpu>-<os> in kleine letters, naast het uitvoerbare bestand, en die wordt op elk doel gevonden door de eerste stap van de keten. Het viewer-voorbeeld dat dit op Lazarus oefent wordt beschreven in het artikel over de Lazarus- en FPC-viewer, en de huidige platformondersteuning staat op de productpagina van de PDFium Delphi component