Teknisk artikkel

Laste PDFium native-biblioteket på ethvert mål

PDFium-komponenten finner sitt native bibliotek gjennom en fast, ordnet søkekjede snarere enn å overlate det til operativsystemets loader, for et utrullingstre som er eksplisitt, er et utrullingstre du kan feilsøke. På Windows ser den kjeden etter en Win32- eller Win64-underkatalog installasjonsprogrammet allerede leverer. På andre mål bygger den underkatalognavnet fra Free Pascal-målmakroene, som <cpu>-<os>, slik at utrullingstreet leser nøyaktig som det kompilerte enhetstreet. Den siste avgjørelsen innførte en feil som er verdt hele artikkelen, fordi årsaken var en stor bokstav og symptomet var stillhet

Kjeden, i rekkefølge

Fire plasseringer, prøvd i rekkefølge, deretter plattformloaderen som siste utvei. Først det foretrukne oppsettet, en DLLs-katalog ved siden av den kjørbare filen som inneholder én underkatalog per mål. For det andre et alternativt oppsett med målunderkatalogen rett ved siden av den kjørbare filen. For det tredje det flate legacy-oppsettet, biblioteket som ligger ved siden av den kjørbare filen uten noen underkatalog i det hele tatt. For det fjerde, bare på Windows, systemkatalogen, som trenger omsorg fordi en 32-bits prosess må se i SysWOW64 og en 64-bits prosess i System32, og på 32-bits Windows finnes ikke den førstnevnte, så oppslaget må falle tilbake. Først etter alt det blir loaderen bedt om å søke på egen hånd

Diagram over søkekjeden for PDFium native-bibliotek for Delphi, fra DLLs-målunderkatalogen gjennom alternativt, flatt og Windows systemkatalogoppsett til plattformloaderen
Fire eksplisitte plasseringer sonderes i rekkefølge før OS-loaderen bedes søke på egen hånd

Det er bevisst intet systemkatalogsteg utenfor Windows. Plattformloaderens egen søkevei, drevet av runtime-linker-konfigurasjonen og biblioteksveimiljøet, dekker allerede det terrenget, og å duplisere den i Pascal ville bety å re-implementere regler som varierer per distribusjon. Å diagnostisere feiler i Windows-kjeden er dekket separat i utrulling av PDFium-DLL-en og diagnostisering av lastefeiler

Hvor underkatalognavnet kommer fra

På Windows er det Win32 eller Win64, avgjort av bittheten til den kjørende prosessen snarere enn operativsystemet, for det er det som bestemmer hvilken binærfil som kan lastes. Alle andre steder bygges navnet fra kompilatorens målmakroer slik at en maskin som bygger for to arkitekturer produserer to tydelig adskilte trær, og slik at mappen som holder det native biblioteket, ligger ved siden av mappen som holder de kompilerte enhetene med det samme navnet

function BuildDllSubDir(UseV8: Boolean): string;
begin
{$IFDEF MSWINDOWS}
  if IsWin64 then
    Result := 'Win64'
  else
    Result := 'Win32';
{$ELSE}
  // Kompilatormakroene skriver OS-en med stor forbokstav («Linux», «Darwin»)
  // mens pakkens enhetsoutput-katalog ikke gjør det, så de to er enige
  // bare etter folding. På et kasussensitivt filsystem er den
  // forskjellen hele oppslaget
  Result := LowerCase({$I %FPCTARGETCPU%} + '-' + {$I %FPCTARGETOS%});
{$ENDIF}
end;

Hvorfor en stor bokstav brøt hele kjeden

Kompilatormakroen staver måloperativsystemet med en initial stor bokstav: Win64, Linux, Darwin. Lazarus-pakken skriver sin enhetsoutput inn i en katalog navngitt fra sin egen målvariabel, som er små bokstaver: win64, linux, darwin. To stavemåter av det samme, og ingen måte å legge merke til det på Windows, der filsystemet ikke skiller dem

På Linux er de to forskjellige kataloger. En utrulling som legger det delte objektet i DLLs/x86_64-linux, er usynlig for en loader som leter etter DLLs/x86_64-Linux, så alle fire eksplisitte stegene i kjeden bommer, og koden faller gjennom til å la plattformloaderen søke. Noen ganger fungerer det, hvis biblioteket tilfeldigvis er installert systemvidt, og noen ganger gjør det ikke det, og uansett bidrar det omhyggelig anlagte utrullingstreet med ingenting. Feilen har ingen feilmelding fordi ingenting feilet: hvert steg rapporterte korrekt at filen ikke var der den lette

Hvordan én stor bokstav i FPC-målmakroen bryter PDFium-DLL-oppslaget på Linux: loaderen søker DLLs/x86_64-Linux mens den utrullete mappen er DLLs/x86_64-linux, som bare matcher på kasus-ufølsom Windows
Det samme målet stavet to måter matcher på Windows og bommer lydløst på et kasussensitivt filsystem

Probe-programmet, kompilert og kjørt

Denne klassen av feiler kan ikke finnes ved å lese, og den kan ikke finnes ved å kompilere heller. Den vanlige teknikken for å verifisere en plattformgren som aldri kompilerer på utviklingsmaskinen, er å kopiere enheten til en midlertidig katalog, gi den nytt navn, erstatte plattformbetingelsen med et symbol som aldri er definert, og kompilere kopien; hvis den kompilerer, er uses-klausulen og kallsignaturene på den veien i det minste selv-konsistente. Det fungerer godt for en selvstendig enhet

Det fungerer ikke her. Hovedbindingsenheten er svært stor og trekker inn LCL, så den kan ikke rett og slett kopieres og kompileres med Windows-symbolet slått av. Så i stedet ble håndfull funksjoner endringen rørte, transkribert ordrett inn i et lite selvstendig program, og det programmet ble kjørt. Det skrev ut x86_64-Win64, og avviket var synlig i én linje output. Å kompilere det samme programmet ville ha fortalt deg ingenting, fordi strengen er helt gyldig; bare dens verdi er feil

program ProbeSubDir;
{$MODE DELPHI}
uses
  SysUtils;
begin
  // Skriv ut, ikke påstå. Poenget er å se på verdien en makro
  // faktisk ekspanderer til på denne verktøykjeden
  Writeln('raw:    ', {$I %FPCTARGETCPU%}, '-', {$I %FPCTARGETOS%});
  Writeln('folded: ', LowerCase({$I %FPCTARGETCPU%} + '-' +
    {$I %FPCTARGETOS%}));
end.

Den generelle leksjonen: når en tvers-plattform-endring angår verdien av noe snarere enn dens type, er kun-kompilering-verifisering ikke verifisering. Skriv den ut. Det bredere settet av tvers-kompilator-forskjeller mellom Delphi og Free Pascal er samlet i artikkelen om Delphi- og FPC tvers-kompilator-fellgruver

La plattformen forklare sine egne lastefeiler

Windows-grenen av loaderen enumererer for hånd årsakene en last kan feile, fordi de nyttige skillene der, et arkitekturavvik, en manglende transitiv avhengighet, en vei som ikke løses, mapper til feilkoder verdt å navngi individuelt. Utenfor Windows returnerer den portable loader-enheten allerede en beskrivende streng som dekker det samme terrenget, så ikke-Windows-grenen bruker den direkte snarere enn å re-utlede kategorier fra et feilnummer som betyr forskjellige ting på forskjellige systemer

Å motstå trangen til å normalisere de to inn i én melding, er bevisst. En lastefeil er et utrullingsproblem, og personen som leser meldingen, trenger plattformens eget vokabular for å søke etter det

En navnekollisjon som rekurrerer

Én felle til, liten og skarp. Den portable loader-enheten eksporterer en prosedyre kalt UnloadLibrary, og bindingsenheten har en prosedyre med det samme navnet som gjør sin egen bokføring før den slipper håndtaket. Inne i den prosedyren løses et ukvalifisert kall til UnloadLibrary til den i den nåværende enheten, som kaller seg selv. Fiksen er å kvalifisere kallet med enhetsnavnet

Dette er den samme formen som identifikatorskyggeleggingsproblemene som dominerer Free Pascal-porteringer generelt: Windows-enheten eksporterer heltallstyped minimum- og maksimumfunksjoner som skygger de flyttallstyped, og en synkroniseringstype som skygger klassen med det samme navnet, og i hvert tilfelle avhenger oppløsningen av rekkefølgen av uses-klausulen. Å kvalifisere kallstedet er fiksen som ikke avhenger av at noen bevarer den rekkefølgen senere

UnloadLibrary-navnekollisjon i PDFium-komponenten: et ukvalifisert kall i Delphi-bindiingsenheten rekurrerer inn i seg selv, mens et enhetskvalifisert kall når den portable loader-enheten og slipper håndtaket
Å kvalifisere kallstedet sender utslipp gjennom loader-enheten i stedet for å rekurrere inn i bindingsenheten

Utrullingssjekkliste

Tre ting står for de fleste lastefeiler når veiaritmetikken er riktig. Arkitekturen må matche prosessen, ikke maskinen, så en 32-bits applikasjon på 64-bits Windows trenger 32-bits binærfilen. V8-aktivert bygget har et annet filnavn, så en utrulling som blander dem, vil se korrekt ut og laste ingenting. Og bare én variant kan bo i en systemkatalog om gangen, noe som er en god grunn til å foretrekke det eksplisitte underkatalogoppsettet fremfor å installere noe systemvidt

For Lazarus spesifikt, legg det native biblioteket under DLLs/<cpu>-<os> med små bokstaver, ved siden av den kjørbare filen, så finnes det av det første steget i kjeden på hvert mål. Fremviserprøven som trener dette på Lazarus, er beskrevet i artikkelen om Lazarus- og FPC-fremviseren, og nåværende plattformstøtte er oppført på produktsiden for PDFium Delphi component