Techninis straipsnis

Free Pascal Win32: C simbolių dekoravimas HotPDF

Free Pascal Win32 aplinkoje kiekvienam cdecl; external importui automatiškai prideda pabraukimą priekyje, o public name eksportuoja jūsų parašytą eilutę žodį į žodį. HotPDF turi patenkinti abi konvencijas toje pačioje pirminio kodo šakoje, nes Delphi statyba jau siunčia importo deklaracijas, kuriose pabraukimas įrašytas ranka. Čia asimetriją sugadinus gaunamos susiejimo klaidos, įvardijančios simbolį, kurio niekas nėra rašęs

Delphi bibliotekos išplėtimas iki Free Pascal paprastai aprašomas kaip perkeliamumo problema, ir Win64 aplinkoje tai daugiausia ir yra perkeliamumo problema. Win32 kitoks. 32 bitų x86 Windows ABI neša trisdešimt metų sukauptų konvencijų apie tai, kaip rašomi C simboliai, kas valo steką ir kokiems kompiliatoriui privatiems pagalbininkams vertimo vienetas gali tikėtis, ir kiekviena iš tų vietų yra ta, kur du kalboje sutantys Pascal kompiliatoriai vis dar gali nesutarti dėl objektinio failo

Kodėl tas pats simbolis Win64 išspręndžia, o Win32 žlunga?

Todėl, kad pabraukimo priešdėlis yra 32 bitų konvencija, kurią Free Pascal taiko importams, bet ne eksportams. Deklaruokite function deflate(...): Integer; cdecl; external; ir FPC Win32 objektiniame faile ieškos _deflate, o Win64 — deflate. Tai teisinga elgsena ir sutampa su tuo, ką išleidžia C kompiliatorius. Spąstai kitoje tilto pusėje: rutina, pažymėta public name 'deflate', abiem taikiniams eksportuoja tiksliai deflate be jokio pridėto priešdėlio

Dabar pridėkite istorinę detalę, kuri tai padaro konkrečiu. Delphi statyba kai kuriuos šiuos įėjimo taškus jau deklaruoja su pabraukimu, įrašytu į patį vardą, nes būtent tai yra jos pačios objektiniuose failuose. Tą pačią deklaraciją duodami FPC Win32, kompiliatorius ugdai priešdeda priešdėlį dar kartą, ir linkeris gaudo __deflate — simbolį, kurio niekas neeksportuoja. Intuityvus sprendimas, pridėti vieną pabraukimą visur, sulaužo importus, kurie jau buvo parašyti teisingai

Veikia priešdėlių konstantų pora, o ne viena. HPDFFPCZLib ir HPDFFPCCodecStubs naudoja vieną priešdėlį paprastiems C importams ir kitą importams, kurie jau neša Delphi pusės priešdėlį, o Win64 abi konstantos tuščios, todėl esami susiejimo vardai išgyvena nepaliesti. Dvi konstantos vietoje vienos yra visas sutvarkymas, ir tai akivaizdu tik tada, kai atskirėte importo taisyklę nuo eksporto taisyklės

Tos pačios C simbolių deklaracijos, išspręstos Free Pascal ir Delphi Win64 ir Win32: cdecl importai pabraukimą gauna tik 32 bitų taikinyje, Delphi deklaracija, kurioje pabraukimas jau įrašytas, pavirsta __deflate ir nebesusieja, o public name eksportai abiem architektūromis lieka litera
Viena priešdėlio konstanta abiejų taisyklių nedengia: paprasti cdecl importai ir importai, jau nešantys Delphi pabraukimą, po FPC Win32 dekoruojami kitaip, todėl HotPDF laiko dvi ir Win64 palieka abi tuščias
// Du priešdėliai, o ne vienas: paprasti C importai ir importai, jau nešantys
// ranka rašytą Delphi priešdėlį, po FPC/Win32 dekoruojami kitaip
const
{$IF DEFINED(FPC) and DEFINED(CPU32)}
  CPrefix     = '_';   // FPC pats tai prideda cdecl external atveju
  DelphiCName = '';    // šaltinyje pabraukimas jau įrašytas
{$ELSE}
  CPrefix     = '';
  DelphiCName = '';
{$IFEND}

// Eksporto pusė: 'public name' kiekviename taikinyje litera
procedure hpdf_codec_free(P: Pointer); cdecl;
  public name 'hpdf_codec_free';

WIN32 pasako architektūrą, o ne ABI

Tai sąlyginės kompiliacijos klaida su ilgiausia derinimo uodega, ir verta pasakyti tiesiai: WIN32 ir WIN64 apibūdina taikinio architektūrą ir nieko nesako apie tai, kokie kompiliatoriui privatūs runtime pagalbininkai egzistuoja. Free Pascal apibrėžia abu simbolius atitinkamuose Windows taikiniuose, tiksliai kaip ir Delphi. Todėl apsauga {$IFDEF WIN32} aplink kodą, kviečiantį Delphi runtime pagalbininką, po FPC sukompiliuojasi, o susiejimo metu žlunga

Konkrečiai, į šiuos spąstus įkrenta trys kodo šeimos. Delphi 64 bitų sveikųjų skaičių trampolinos, pasiekiamos per System.@_ll pagalbininkus, MSVC Win32 asemblerio palaikymo rutinos ir su jomis einantys importo lizdai visi egzistuoja tarnaudami ikikompiliuotiems C objektams, kuriuos susieja Delphi statyba. Free Pascal tų objektų nesusieja, todėl jam nereikia nė iš tos mašinerijos, ir kiekviena nuoroda į ją turi dingti. Subtilumas tame, kad deklaracija ir realizacija turi būti išskirtos kartu. Išskyrus tik vieną, kompiliatorius praneša niekuo nepadedinčią žinutę apie identifikatorių, kurio nesuderina su niekuo

Iš to išeinanti taisyklė trumpa. Sargybinį dėkite pagal kompiliatorių, kai klausimas apie ABI arba runtime palaikymą, pagal architektūrą — kai klausimas apie rodyklės plotį ar registrų skaičių, ir niekada neleiskite vienam stovėti kito vietoje

Deklaracijų ir realizacijų sargyba kartu

Į sąsajos sekcijos sąlyginį bloką lengva įkristi nepastebėjus, o gauta klaidos žinutė rodo bet kur, tik ne į priežastį. Pridėję metodo deklaraciją prie klasės sąsajos, natūraliausią jos vietą pasirinksite šalia susijusių metodų, ir tai gerai iki pat akimirkos, kai tie kaimynai pasirodo esą viduje esamo {$IFDEF} bloko. Sąlyginės direktyvos nėra įtraukiamos, todėl blokas, atsivėręs keturiasdešimt eilučių aukščiau, esmingai nematomas, kol skaitote aplinkines deklaracijas

Tolesnis įvykis — kompiliacija, kuri vienoje įrankių grandinėje sėkminga, o kitoje duoda laviną. Jei aplinkinė apsauga yra Delphi versijos patikra, kurios Free Pascal netenkina, deklaracija FPC dingsta, nesąlyginė realizacija lieka, ir kompiliatorius praneša ilgą skundų sąrašą apie metodus, kurių tikėjosi ir nerado. Nė viena žinutė neminės sąlyginio bloko, tai sukėlusio

Du įpročiai apsaugo nuo visos šios gedimų klasės. Prieš įrašydami į sąsajos sekciją, pažiūrėkite aukštyn ieškodami artimiausios atviros sąlygos, o nesitikėkite vizualinio grupavimo. Ir žalią Delphi testų rinkinį laikykite įrodymu tik apie Delphi: Free Pascal bibliotekos statyba yra atskiri vartai, ir vienintelis būdas žinoti, kad ji praeina, yra paleisti build-Win32-Lib-FPC.cmd ir build-Win64-Lib-FPC.cmd to paties pakeitimo dalimi

Kas lūžta 32 bitų aritmetikos kode

Vienas kalbos apribojimas pasirodo būtent tame kode, kuris mažiausiai linkęs keistis: 32 bitų Free Pascal nepriims UInt64 kaip for ciklo valdymo kintamojo. Elipsinės kreivės moduliuose, nešančiuose X25519 ir X448, šerių masyvus einantys ciklai buvo parašyti su 64 bitų skaitikliais vien todėl, kad visa kita faile yra 64 bitų

Sprendimas turi būti chirurginis, nes lauko aritmetikoje kintamojo plotis yra teisingumo argumento dalis. Ciklų indeksai tampa Integer, nes šerių masyvas turi saujelę elementų ir joks indeksas niekada nepriartėja prie 32 bitų ribos. Viskas, kas dalyvauja aritmetikoje — patys šeriai, pernešimų skleidimas ir kaukės — lieka UInt64, nes bet kurio iš jų susiaurinimas tyliai pakeičia rezultatą moduliu lauko pirminio skaičiaus

// 32 bitų FPC atmeta UInt64 ciklo kintamąjį. Siaurinkite tik indeksą;
// šeriai, kaukės ir pernešimai lieka tokio pločio, kitaip keičiasi lauko matematika
var
  I: Integer;                 // anksčiau buvo UInt64
  Carry, Mask: UInt64;
begin
  Carry := 0;
  for I := 0 to High(Limbs) do
  begin
    Limbs[I] := Limbs[I] + Carry;
    Carry := Limbs[I] shr 51;
    Limbs[I] := Limbs[I] and Mask;
  end;
end;

Tokia pakeitimo patikra negali būti apvalaus kelio testas. Šifravimas ir iššifravimas ta pačia sugedusia realizacija puikiai sutampa pats su savimi, todėl žinomo atsakymo vektoriai čia nėra derybų objektas: paleiskite publikuotus X25519 ir X448 testų vektorius ir palyginkite tikslius išvesties baitus. Tai vienintelė patikra, atskirianti teisingą realizaciją nuo su savimi suderintos neteisingos, ir ji lygiai taip pat galioja simetrinėms primityvoms, nagrinėjamoms straipsnyje Free Pascal deflate ir AES codec ribos

Du HotPDF Win32 Free Pascal statybos lūžio taškai: {$IFDEF WIN32} apsauga aplink Delphi runtime pagalbininkus, kuri sukompiliuojasi, bet žlunga susiedama, nebent deklaracija ir realizacija išskirtos kartu, ir UInt64 ciklo kintamasis X25519 bei X448 šerių einimuose, susiaurintas iki Integer, kol šeriai, pernešimai ir kaukės išlaiko savo plotį
Sargybinį dėkite pagal kompiliatorių, kai klausimas ABI arba runtime palaikymas, ir pagal architektūrą, kai jis apie rodyklės plotį, o aritmetikos pakeitimus įrodykite publikuotais žinomo atsakymo vektoriais, o ne apvalaus kelio testais

Už ką verta Win32 Free Pascal statyba

Praktinė išmoka: Lazarus programa, taikanti 32 bitų Windows, gauna tą patį dokumentų variklį kaip ir jos Delphi atitikmuo, be atskiro dvejetainio kontrakto, kurį reikėtų prižiūrėti. Tai svarbiausia tiems diegimams, apie kuriuos retai kalbama: pramoniniai valdikliai, kasos terminalai ir ilgaamžė verslo linijos programinė įranga, kur 32 bitų runtime nėra palikimo pasirinkimas, o aparatinis apribojimas

Win64 istorija atėjo pirma ir aprašyta straipsnyje Free Pascal ir Lazarus palaikymas Win64. Win32 nėra jos pakartojimas. Win64 turi vieną iškvietimo konvenciją, jokio vardų dekoravimo ir jokių Delphi privačių sveikųjų skaičių pagalbininkų, kuriuos reikėtų apeiti, todėl beveik viskas šiame straipsnyje specifiška 32 bitų taikiniui. Aritmetikos moduliai, kuriems reikėjo ciklo kintamojo pakeitimo, yra tie patys, aprašyti straipsnyje Montgomery aritmetika ant NIST kreivių, kur plotio drausmė paaiškinta giliau

Bendra pamoka: kryžminio kompiliatoriaus perkeliamumo darbas pirmiausia nėra apie kalbos galimybes. Abu kompiliatoriai čia priima tą patį Object Pascal. Skiriasi objektinis failas: kaip rašomi simboliai, kokių pagalbinių rutinų tikimasi iš runtime ir kurie ikikompiliuoti objektai yra susiejime. HotPDF siunčia Free Pascal ir Lazarus paketus kartu su Delphi ir C++Builder paketais HotPDF Delphi PDF komponente, todėl ta pati pirminio kodo šaka maitina kiekvieną įrankių grandinę, o ne atšakojama pagal kompiliatorių