PDFlibPas statomas su Free Pascal 32 bitų Windows aplinkoje, ir sunkiausia dalis niekada nebuvo pats Pascal. Sunkiausia dalis buvo objektiniai failai: AES ir OpenJPEG objektai, kuriuos susieja Delphi statyba, yra OMF formato, Free Pascal vidinis linkeris reikalauja COFF, o konvertavimas tarp šių dviejų duoda sekcijų pavadinimus ir sekcijų apibrėžimo simbolius, dėl kurių linkeris žlunga su vidinėmis klaidomis, o ne su normalia diagnostika
Kas bet kada yra susiejęs C objektus į Pascal biblioteką, tas šitą teritoriją pažįsta. Win64 yra palyginti civilizuotas: vienas objektų formatas, viena iškvietimo konvencija ir jokio vardų dekoravimo. Win32 išsaugo kiekvieną platformos sukauptos istorijos sluoksnį, o biblioteka, statinėmis susiejanti trečiųjų šalių C kodą, su visais jais susiduria vienu metu
Kompiliatoriaus katalogas nepasako, kokį taikinį statote
Pradėkite nuo statybos įėjimo taško, nes čia apsiklaidus prarandate valandas, dar nieko neturint su objektiniais failais. Free Pascal diegimo katalogo pavadinimas nurodo, kur gyvena pagrindinis kompiliatorius, bet ne tai, ką jis pagamina. 32 bitų pagrindinis kompiliatorius gali iškviesti šalia esantį kryžminį kompiliatorių ir išleisti 64 bitų kodą, kai perduodate tinkamus taikinio jungiklius, todėl taikinio spėjimas iš kelio yra atspėjimas, kuris veikia tol, kol kas nors persidėlioja savo įrankių grandinę
Pasikliautinas kelias — paklausti paties kompiliatoriaus. Faktinį taikinio procesorių ir operacinę sistemą pasiteiskite per kompiliatoriaus informacinius jungiklius, o priimkite abu įprastus diegimo išdėstymus — plokščią binarinį katalogą ir versijomis įdėtąjį, nes skirtingi diegėjai ir įrankių valdytojai gamina skirtingas formas. Statybos scenarijus, kuriame vienas išdėstymas įkaltas standžiai, veikia tiksliai vienoje mašinoje
Kodėl konvertuotas objektinis failas laužia vidinį linkerį?
Todėl, kad konvertavimas išsaugo OMF sekcijų vardų konvenciją ir prisigalvoja sekcijų apibrėžimo simbolius, kurie neatitinka to, ko tikisi COFF linkeris. OMF objektų konvertavimas į COFF yra būtinas, bet nepakankamas: gauti failai neša klasikinius _TEXT, _DATA ir _BSS sekcijų vardus, plius iš jų išvestus sekcijų apibrėžimo simbolių vardus, ir tai įdėjus į Free Pascal vidinį linkerį gaunami vidinės kompiliatoriaus klaidos, o ne pranešimas apie sekcijų vardijimą
Vidinė klaida yra pats blogiausias statybos problemos gedimo režimas, nes apie tai, kas blogai su įvestimi, nesako absoliučiai nieko. Gelbėja konvertavimo paskirta normalizavimo peržiūra per COFF failą: perrašykite sekcijų vardus į laukiamą formą ir perrašykite atitinkamus sekcijų apibrėžimo simbolius, kad sutaptų, o simbolio indeksą, kodo baitus ir perkėlimus palikite nepaliestus. Būtent šis paskutinis apribojimas ir yra visa sudėtingumas. Perrašymas, kuris pernumeryja simbolius arba paslenka poslinkius, duoda objektą, kuris susisieja, o po to nulūžta
Vienam iš dviejų objektų rinkinių reikia ir parengiamo žingsnio. OpenJPEG objektai, pastatyti klasikinio 32 bitų C++ kompiliatoriaus, priklauso nuo privačių Delphi 64 bitų sveikųjų skaičių pagalbinių procedūrų, kurių Free Pascal neduoda, todėl joks formatų konvertavimo kiekis jų nepaverčia tinkamais naudoti. Tie objektai pirmiausia perstatomi Clang pagrindo kompiliatoriumi, kuris tų priklausomybių neišleidžia, o po to konvertuojami
// FPC taikinio objektai gyvena savame kataloge. Jie nepakeičia Delphi
// objektų rinkinio, nes abu įrankių rinkiniai stato iš tos pačios
// pirminio kodo šakos ir kiekvienam reikia savų susiejimo priklausomybių
//
// Lib\thirdparty\Win32 Delphi OMF objektai, nepaliesti
// Lib\thirdparty\Win32f FPC COFF objektai, konvertuoti ir normalizuoti
//
// Statybos įėjimo taškai:
// build-Win32-Lib-FPC.cmd
// build-Win64-Lib-FPC.cmd
Kompiliatoriui privatūs pagalbininkai nėra perkeliami, ir jų konvencijos taip pat
Delphi runtime 32 bitų x86 procesoriui duoda asemblerio trampolinas 64 bitų sveikųjų skaičių operacijoms, ir Delphi skirti ikikompiliuoti C objektai į jas kviečia. Free Pascal turi savą sutvarkymą, todėl tas nuorodas reikia patenkinti kitaip, o ne peradresuoti. Detalė, dėl kurios peradresavimas neįmanomas, yra iškvietimo konvencija: laiko matavimo pagalbininkas, kurį naudoja vaizdų apdorojimo kodas, savo keturių baitų argumento valymą palieka kviečiamajai pusei, o 64 bitų dalybos pagalbininkas išvalo šešiolika baitų ir grąžina rezultatą klasikinėje registrų poroje. Du pagalbininkai, dvi konvencijos, ir vienam parašyta trampolina kitam tyliai sugadina steką
Antra problemos pusė — vardų dekoravimas. Win32 aplinkoje Free Pascal išoriniams C importams automatiškai prideda pabraukimo priešdėlį, o public name deklaracijas eksportuoja žodis į žodį, todėl to paties tilto importo ir eksporto pusės gyvena pagal skirtingas taisykles. C runtime tiltas, kurio reikia OpenJPEG, turi eksportuoti tikslius C simbolių vardus, o variadic įėjimo taškai reikalauja 32 bitų netiesinio šuolio, o ne tiesioginio. Išgirdus niekas čia nėra egzotiška. Praktikoje visa tai žlunga kaip susiejimo klaida, įvardijanti simbolį, kurio niekas nėra parašęs
Kas Win32 vykdomąjį failą užmuša dar iki main?
64 bitų DLL paieškos kelyje, į kurį įkliūvoma todėl, kad Free Pascal zlib modulis rišasi dinamiškai, o ne susiejamas statinėmis. Simptomas buvo momentinis išėjimas su invalid-image statuso kodu, dar neįvykdžius nė vienos programoje esančios Pascal eilutės, ir tai siunčia jus žiūrėti į ką tik pastatytą programą, kai gedimas slypi įkėlyje, kuris importą susieja su netinkamos architektūros biblioteka
Pamoka čia apie prielaidas, o ne apie zlib. Modulis, pavadintas suspaudimo bibliotekos vardu, nebūtinai jos viduje turi; gali būti, kad tai tik susiejimas, kuris vykdymo metu tikisi bendro naudojimo bibliotekos, o nenumanoma dinaminė priklausomybė yra diegimo rizika net tada, kai jai pasiseka išspręstis. Perėjimas prie grynos Pascal srauto realizacijos abiem taikiniams duoda statinėmis įtrauktą suspaudimo kelią be jokios išorinės priklausomybės — tai, ko biblioteka, kurią kiti įkomponuoja į savas programas, ir turėtų norėti nuo pat pradžių
Tas pats instinktas galioja ir išoriniam JBIG2 koduotojo backend'ui. 32 bitų taikinyje išorinis koduotojas nėra susiejamas, todėl užklausos nukrenta į įtaisytąjį Pascal koduotoją, ir šį faktą tikrinantis testas turi patikrinti dabartinio taikinio registracijos būseną, o ne sėkmingą kodavimą laikyti įrodymu, kad išorinis backend yra vietoje. Veikiantis fallback yra tiksliai tas dalykas, kuris slepia trūkstamą priklausomybę, ir tai yra gedimo modelis, nagrinėjamas straipsnyje tylių stub'ų gedimų diagnostika. 64 bitų statinio susiejimo darbas aprašytas straipsnyje jbig2enc statinis susiejimas FPC aplinkoje
32 bitų aritmetika atminties sraute
Buferio dydžius valdantis kodas su rodyklės pločio beženklia aritmetika Win64 yra teisingas, o Win32 jį nuo persipildymo teskiria vienas didelis vaizdas. Atmintyje gyvenantis srautas, kuriuo maitinamas JPEG 2000 codec, auga dvigubindamasis ir slenka sudėdamas, o 32 bitų taikinyje abi operacijos gali persisukti ties įvestimis, kurios didelės, bet visiškai teisėtos
Kiekvienas įrašymas, praleidimas, pozicionavimas ir pradinis išskyrimas todėl pirmiausia patikrina, o tik paskui skaičiuoja, ir talpos lubos yra didžiausia suženklė rodyklės pločio reikšmė, parinkta pagal tai, ką gali išreikšti bloko perkėlimo rutina ir callback grąžinamos reikšmės. Elgesio reikalavimas, kai užklausa atmetama, lengvai sugadinamas: atsisakymas negali pakeisti nei srauto pozicijos, nei jo ilgio. Dalinis pakeitimas, už kurį seka klaida, palieka srautą būsenoje, apie kurią kviečiančioji pusė nieko protingo pasakyti negali, o kita operacija tai dar užpila
// Patikrinkite prieš skaičiuodami. Win32 abu šie veiksmai persisuka ties
// įvestimis, kurių teisėtai pagamina didelis JPEG 2000 vaizdas
if Needed > NativeUInt(High(NativeInt)) - FPosition then
Exit(False); // atsisakyti, pozicijos ir dydžio nekeisti
NewCapacity := FCapacity;
while (NewCapacity < FPosition + Needed) do
begin
if NewCapacity > NativeUInt(High(NativeInt)) shr 1 then
Exit(False); // padvigubinimas persipildytų
NewCapacity := NewCapacity shl 1;
end;
Du statybos išvesties spąstai, išgyvenantys patį pernešimą
Testų ir pavyzdinių vykdomųjų failų išskyrimas pagal taikinio architektūrą į atskirus kiekvieno taikinio išvesties katalogus yra akivaizdžiai teisinga ir tučtuojau sulaužo viską, kas savo testų duomenis surasdavo skaičiuodamas katalogų lygius aukštyn. Sprendimas — ieškoti resursų katalogo kylančiu aukštyn, o ne manyti fiksuotą gylį, su vienu sąmoningu apribojimu: pasirašymo pavyzdys liudijimo fallback'ą priima tik iš savo projekto katalogo, niekada iš bet kurio aukštesniojo, nes tokiu pačiu vardu pavadintas liudijimas, rastas aukščiau medyje, yra saugumo netikėtumas, o ne patogumas
Antri spąstai išgyvena kiekvieną pernešimą, ir verta juos parsinešti į bet kurį FPC projektą. Po kompiliatoriaus atnaujinimo užtepti, kad kompiliatorius atmestų pasenusius PPU failus, nepakanka, nes linkeris vis tiek pirmena modulių paieškos kelyje paliktus objektinius failus, net kai pakrautas PPU atėjo iš teisingo katalogo, o aiškaus objektų išvesties kelio pridėjimas tos pirmenybės nepakeičia. Vienas patikimas atsakymas — švarus laikinas modulių katalogas kiekvienai statybos kraitei. Viskas, kas paprasčiau, duoda dvejų kompiliatoriaus versijų susietą dvejetainį failą, kuris žlunga taip, kad atrodo kaip pirminio kodo klaidos
Platformos sąlyginės kompiliacijos yra paskutinis gabalas, ir teisingos ašies pasirinkimas svarbesnis, nei atrodo. Teisingas klausimas paprastai yra, ar kodas specifinis Windows, o ne ar tam tikra valdiklių biblioteka yra vietoje, kaip parodė metafile konvertavimo darbas, aprašytas straipsnyje EMF vektorių importas ir platformos sąlygos: tos apsaugos perjungimas nuo valdiklių bibliotekos sąlygos į platformos sąlygą tariamą perrašymą pavertė vienos direktyvos pakeitimu. Free Pascal ir Lazarus palaikymas abiem Windows taikiniams keliauja kartu su PDFlibPas Delphi PDF biblioteka, statoma iš tų pačių pirminio kodo šaltinių kaip ir Delphi bei C++Builder paketai