Kun pdfium.dll kieltäytyy latautumasta asiakaskoneella, Delphille ja Lazarukselle tarkoitettu PDFium-komponentti muuttaa kryptisen natiiviraportin tarkaksi syyksi. CheckLoadLibrary-rutiini tulkitsee virheen ERROR_BAD_EXE_FORMAT (193) 32/64-bittiseksi arkkitehtuuriristiriidaksi, ja CheckGetProcAddress raportoi puuttuvan vientifunktion (export) merkkinä siitä, että käytössä oleva pdfium.dll on vanhempi kuin sidonta. Molemmat viestit nimeävät korjaustoimenpiteen sen sijaan, että joutuisit arvailemaan
Tällä on merkitystä, koska natiivi DLL voi epäonnistua kolmella eri tavalla, ja käyttöjärjestelmän raaka virheteksti sekoittaa ne kaikki keskenään. Arkkitehtuuri voi olla väärä, käyttöönotettu binaari voi olla liian vanha sitä kutsuvalle Pascal-sidonnalle, tai kaksi säiettä voi kilpailla sen sitomisesta samalla hetkellä. Jokaisella virheellä on eri korjaus, ja v2.11.0 latauksen elinkaareen lisättyjen diagnostiikkojen tarkoituksena on kertoa, mitä niistä todellisuudessa katsot
Miksi pdfium.dll epäonnistuu latauksessa virheellisen EXE-muodon takia?
Windows-virhe 193, ERROR_BAD_EXE_FORMAT, tarkoittaa, että tiedosto löydettiin ja avattiin, mutta sen PE-konetyyppi (PE machine type) ei vastaa isäntäprosessia. 64-bittinen ajettava tiedosto (executable) ei voi ladata 32-bittistä pdfium.dll-tiedostoa, eikä 32-bittinen ajettava tiedosto voi ladata 64-bittistä. Tiedosto on olemassa, joten vaisto etsiä puuttuvaa DLL-tiedostoa ohjaa sinut täysin väärään suuntaan
PDFium-komponentti sieppaa tämän tapauksen CheckLoadLibrary-metodissa: kun LoadLibrary palauttaa nollakahvan ja GetLastError on 193, se lisää vihjeen, että DLL löydettiin mutta sen arkkitehtuuri ei vastaa isäntäprosessia, ja osoittaa vastaavaan DLLs/Win32- tai DLLs/Win64-koontiversioon. Alihakemisto valitaan BuildDllSubDir-funktiolla, joka palauttaa arvon Win64, kun IsWin64 on tosi, ja muuten Win32. Ota DLL käyttöön sen puun alla, jota sidonta todella etsii, niin ristiriita häviää
var
Pdf: TPdf;
begin
// Ota pdfium.dll käyttöön ajettavan tiedoston vieressä, vastaten koontikohdetta:
// <AppDir>\DLLs\Win32\pdfium.dll 32-bittiselle isäntäprosessille
// <AppDir>\DLLs\Win64\pdfium.dll 64-bittiselle isäntäprosessille
Pdf := TPdf.Create(nil);
try
Pdf.FileName := AFileName;
try
Pdf.Active := True; // ensimmäinen käyttö sitoo pdfium.dll-tiedoston laiskasti
except
on E: EPdfError do
// Viesti nimeää jo todellisen syyn: 32/64-bittinen ristiriita
// (ERROR_BAD_EXE_FORMAT) tai tätä sidontaa vanhempi pdfium.dll
raise Exception.CreateFmt('PDF engine did not start: %s', [E.Message]);
end;
if Pdf.Active then
RenderFirstPage(Pdf);
finally
Pdf.Free;
end;
end;
Mitä puuttuva PDFium-vientifunktio kertoo?
Toinen virheluokka on versioero (version skew). PDFium julkaisee uusia vientifunktioita ajan myötä, ja Pascal-sidonta selvittää jokaisen tarvitsemansa funktion CheckGetProcAddress-kutsulla LoadLibrary-tapahtuman aikana. Jos vaadittu vientifunktio puuttuu, sidonta on uudempi kuin levyllä oleva pdfium.dll, ja rehellinen diagnoosi on, että käyttöönotettu DLL on vanhentunut eikä suinkaan korruptoitunut. CheckGetProcAddress-funktio ilmoittaa juuri tämän: se nostaa poikkeuksen EPdfError raportoiden, että käytössä oleva pdfium.dll on vanhempi kuin tämä PDFiumPas-sidonnan koontiversio, ja se nimeää DLLs/<subdir>-polun, jonne yhteensopiva binaari kuuluu
Kaksi yksityiskohtaa tekee tästä polusta vankan. Ensinnäkin CheckGetProcAddress kutsuu UnloadLibrary-metodia ennen poikkeuksen nostamista, joten osittainen sidonta ei koskaan jätä puoliksi selvitettyjä funktio-osoittimia taakseen seuraavan yrityksen kompastuskiveksi. Toiseksi sen raportoima DLL-nimi tulee BuildDllName-funktiosta, joka palauttaa normaalisti arvon pdfium.dll ja arvon pdfium.v8.dll, kun globaali EnableV8Engine-lippu on asetettu. Jos sovelluksesi ottaa käyttöön V8 JavaScript -moottorin XFA- tai skriptattuja lomakkeita varten, virheteksti osoittaa V8-koontiversioon eikä tavalliseen, joten korvaat oikean tiedoston heti ensimmäisellä kerralla
Onko PDFium DLL turvallista ladata taustasäikeestä?
Kirjaston lataaminen on nyt turvallista mistä tahansa säikeestä; PDFium API:n kutsuminen useasta säikeestä ei vieläkään ole. Nämä ovat kaksi erillistä takausta, ja niiden pitäminen erillään on ero vakaan työaltaan ja ajoittaisen kaatumisen välillä. Taustarenderöinti ajaa tyypillisesti jokaisen sivun renderöinnin TPdfFuture<T>-työntekijän kautta, ja ensimmäinen tulevaisuus (future), joka koskettaa TPdf-luokkaa, laukaisee laiskan sidonnan. Ilman suojausta kaksi työntekijää voisi molemmat havaita lataamattoman kirjaston, molemmat suorittaa sidontasekvenssin, kirjoittaa moduulikahvan yli ja vuotaa ensimmäisen latauksen
Version 2.11.0 työ sulkee tuon ikkunan PDFiumLoadLock-lukolla, joka on prosessinlaajuinen TRTLCriticalSection. Se käärii sisäänsä sekä LoadLibrary- että UnloadLibrary-kutsut. Kriittinen osio tekee tarkista-sitten-lataa-sekvenssistä atomisen, joten mikään kaksi säiettä ei voi molemmat havaita tilaa Loaded=False ja tehdä kaksoissidontaa, eikä mikään säie voi vapauttaa DLL:ää toisen ollessa sidonnan puolivälissä. Lukko luodaan yksikön initialization-osiossa ja puretaan finalization-osiossa suojattuna PDFiumLoadLockReady-lipulla, jotta paritus on turvallinen myös sammutuksen aikana. Jos tarvitset täydellisen työntekijä-ja-vastaus-mallin peruutuksella, artikkeli taustarenderöinti peruutettavilla futuureilla käy sen läpi alusta loppuun
uses
PDFium, FPdfAsync;
// Työntekijämetodi ajetaan taustasäikeessä. Ensimmäinen futuuri joka sitoo
// pdfium.dll-tiedoston sarjoitetaan PDFiumLoadLockin toimesta, joten toinen samanaikainen
// työntekijä ei voi tehdä kaksoissidontaa tai vuotaa moduulikahvaa.
function TReportForm.RenderThumbnail(
const AToken: IPdfCancellationToken): TBitmap;
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'report.pdf';
Pdf.Active := True; // turvallinen samanaikainen sidonta
AToken.ThrowIfCancelled;
Result := Pdf.Thumbnail; // tämän TPdf:n omistaa vain yksi säie
finally
Pdf.Free;
end;
end;
procedure TReportForm.StartRender;
begin
TPdfFuture<TBitmap>.Run(RenderThumbnail, ThumbnailReady);
end;
procedure TReportForm.ThumbnailReady(
const AResult: TPdfFutureResult<TBitmap>);
begin
if AResult.IsSuccess then
Preview.Picture.Assign(AResult.Value);
end;
Mitä latauslukko ei suojaa
Raja tässä on syytä ilmoittaa suoraan, koska korjaus on helppo tulkita väärin. PDFiumLoadLock sarjoittaa vain globaalin sidontatilan: moduulikahvan ja FPDF_*-funktio-osoittimien sijoitukset. Sen alla oleva PDFium C API ei edelleenkään ole säieturvallinen, aivan kuten ylävirran ylätunnisteet (headers) ilmoittavat. Siksi FPDF_*-funktioiden kutsuminen tai yhden FPDF_DOCUMENT-asiakirjan jakaminen säikeiden välillä jää sinun vastuullesi sarjoittaa. Turvallinen muoto on yllä kuvattu: jokainen työntekijä omistaa oman TPdf-luokkansa eikä koskaan luovuta elävää asiakirjaa toiselle säikeelle. Katso lisätietoja tästä rajasta ja sen ympärillä olevista ABI-säännöistä artikkelista PDFium VCL -sidonnan koventaminen ABI- ja muistivirheitä vastaan
Miksi finalization ja FPDF_DestroyLibrary ovat tärkeitä
Hienovaraisempi aukko sijaitsi sammutuspolulla. PDFium-yksikössä ei ollut finalization-osiota, mikä tarkoitti sitä, että FPDF_DestroyLibrary ei koskaan ajettu prosessin päättyessä. Käyttöjärjestelmä lunasti DLL-muistin takaisin ja ohitti PDFiumin oman siivouksen, nimittäin työntekijäsäikeiden liittymisen (worker-thread join), V8-isolaatin hävittämisen, kun EnableV8Engine on asetettu, sekä fontti- ja välimuistipuhdistuksen. Tavallisessa renderöintityömäärässä tämä on hiljainen vuoto purkamisen yhteydessä, mutta V8-moottorin ollessa käytössä se vuotaa JavaScript-isolaatin jokaisella ajokerralla, mikä näkyy nopeasti toistuvassa automaatiossa
finalization-osio kutsuu nyt UnloadLibrary-metodia, joka kutsuu FPDF_DestroyLibrary-funktiota ja vapauttaa moduulin. Tämä sijaitsee yksikön näkyvyysalueella (unit scope) syystä: TPdf.Destroy asettaa vain Active := False sulkeakseen nykyisen asiakirjan, ja se ei tarkoituksella pura globaalia kirjastoa, jotta moni-instanssivaihtoehto pitää yhden jaetun PDFium-sidonnan elossa koko prosessin elinkaaren ajan. Purkamisen sijoittaminen finalization-osioon tarkoittaa, että jaettu kirjasto vapautetaan täsmälleen kerran, kun yksikkö puretaan, ja UnloadLibrary tarkistaa Loaded-lippunsa ensin, joten kutsu on vaaraton no-op, jos PDFiumia ei koskaan käytetty
Lyhyt käyttöönoton muistilista
Kolme tapaa ehkäisee melkein jokaisen latausvirheen kentällä. Sovita DLL-arkkitehtuuri isäntäprosessiin ja toimita se kansioon DLLs/Win32 tai DLLs/Win64. Pidä pdfium.dll samassa tahdissa sidontaversion kanssa, jotta mikään vaadittu vientifunktio ei jää puuttumaan. Äläkä koskaan jaa TPdf- tai asiakirjakahvaa säikeiden välillä, vaikka itse sidonta onkin nyt atominen. Jos rakennat saman lähteen sekä Delphille että Lazarukselle, ristiinkääntämistä vaikeuttavat erot käsitellään artikkelissa Delphi- ja FPC-ristiinkääntäjän sudenkuopat. Diagnostiikka, latauslukko ja finalization-siivous, joita tässä kuvataan, toimitetaan Delphille ja C++Builderille tarkoitetun PDFium-komponentin mukana