A Free Pascal Win32-n minden cdecl; external import elé automatikusan aláhúzást tesz, miközben a public name szóról szóra, betűről betűre exportálja az Ön által írt sztringet. A HotPDFnek ugyanabban a forrásfában kell mindkét konvenciónak megfelelnie, mert a Delphi build már szállít olyan importdeklarációkat, amelyek az aláhúzást kézzel írják ki. Ennek az aszimmetriának az elrontása olyan linkhibákat ad, amelyek egy senki által nem írt szimbólumot neveznek meg
A Delphi könyvtár Free Pascalra bővítését általában hordozhatósági problémaként szokták leírni, és Win64-en többnyire az is. A Win32 más. A 32 bites x86-os Windows ABI harminc év felhalmozott konvencióját hordozza arról, hogyan írnak le a C szimbólumok, ki takarítja le a vermet, és mely fordító privát segédeket tételezhet fel egy fordítási egység, és ezek mindegyike olyan hely, ahol két Pascal fordító, amely egyetért a nyelvben, még eltérhet az objektumfájlban
Miért oldódik fel ugyanaz a szimbólum Win64-en, és bukik meg Win32-n?
Mert az aláhúzás-előtag egy 32 bites konvenció, amelyet a Free Pascal az importokra alkalmaz, az exportokra nem. Deklaráljon egy function deflate(...): Integer; cdecl; external; függvényt, és az FPC Win32-en _deflate-et keres az objektumfájlban, Win64-en deflate-et. Ez helyes viselkedés, és egyezik azzal, amit egy C fordító kiad. A csapda a híd másik oldalán van: egy public name 'deflate'-tel jelölt rutin pontosan deflate-et exportál mindkét célon, előtag nélkül
Most jöjjön az a történelmi részlet, amely konkréttá teszi. A Delphi build egyes belépési pontokat már az aláhúzással együtt deklarál a névben, mert ezt tartalmazzák a saját objektumfájljai. Adja ugyanezt a deklarációt Win32-en az FPC-nek, és a fordító kötelességtudóan újra ellátja előtaggal, tehát a linker __deflate-et keres, egy olyan szimbólumot, amelyet semmi nem exportál. A kézenfekvő javítás, hogy mindenhol berak egy aláhúzást, épp a már helyesen leírt importokat töri meg
Ami működik, az egy előtagkonstans-pár, nem egyetlen konstans. A HPDFFPCZLib és a HPDFFPCCodecStubs egy előtagot használ a sima C importokra és egy másikat azokra az importokra, amelyek már hordoznak Delphi oldali előtagot, Win64-en pedig mindkét konstans üres, tehát a meglévő linknevek érintetlenek maradnak. Két konstans egy helyett az egész javítás, és csak akkor válik nyilvánvalóvá, ha szétválasztotta az importszabályt az exportszabálytól
// Két előtag, nem egy: a sima C importok és a kézzel írt Delphi
// előtagot már hordozó importok másképp díszítenek FPC/Win32 alatt
const
{$IF DEFINED(FPC) and DEFINED(CPU32)}
CPrefix = '_'; // az FPC ezt maga hozzáteszi a cdecl externalhez
DelphiCName = ''; // a forrásban már az aláhúzással együtt leírva
{$ELSE}
CPrefix = '';
DelphiCName = '';
{$IFEND}
// Export oldal: a 'public name' minden célon szó szerinti
procedure hpdf_codec_free(P: Pointer); cdecl;
public name 'hpdf_codec_free';
A WIN32 az architektúrát mondja meg, nem az ABI-t
Ez a feltételes fordítási hiba a leghosszabb hibakeresési farokkal, és érdemes egyszerűen kimondani: a WIN32 és a WIN64 a célarchitektúrát írja le, és semmit sem mond arról, mely fordító privát futásidejű segédek léteznek. A Free Pascal mindkét szimbólumot definiálja a megfelelő Windows célokon, pontosan úgy, mint a Delphi. Egy {$IFDEF WIN32} őr tehát lefordul FPC alatt, és linkeléskor bukik meg, ha Delphi futásidejű segédet hívó kódot fog közre
Konkrétan három kódcsalád esik ebbe a csapdába. A Delphi 64 bites egész trampoline-ok, amelyeket a System.@_ll segédeken keresztül érnek el, az MSVC Win32 assembly támogató rutinok, és a hozzájuk tartozó importhelyek mind azért léteznek, hogy kiszolgálják azokat az előre lefordított C objektumokat, amelyeket a Delphi build linkel. A Free Pascal ezeket az objektumokat nem linkeli, tehát egyik gépezetre sem szüksége, és minden rájuk való hivatkozásnak el kell tűnnie. A finomság az, hogy a deklarációt és az implementációt együtt kell kizárni. Csak az egyiket kizárva a fordító valami használhatatlant jelent egy olyan azonosítóról, amelyet semmihez sem tud párosítani
A levezetett szabály rövid. Akkor őrizzön a fordítóra, ha a kérdés az ABI vagy a futásidejű támogatás, és akkor az architektúrára, ha a kérdés a pointer-szélesség vagy a regiszterszám, és soha ne engedje, hogy az egyik a másikat helyettesítse
Deklarációk és implementációk őrözése együtt
Egy interfész szakaszbeli feltételes blokkba könnyű belecsúszni észrevétlenül, és a kapott hibaüzenet bárhová mutat, csak az okhoz nem. Ha egy metódusdeklarációt vesz fel egy osztályinterfészbe, a kézenfekvő hely a rokon metódusok mellett van, ami pontosan addig rendben van, amíg e szomszédok éppen egy meglévő {$IFDEF} blokkon belül ülnek. A feltételes direktívákat nem húzzuk be, tehát egy negyven sorral feljebb nyíló blokk lényegében láthatatlan, miközben a környező deklarációkat olvassa
Amit azután kap, egy fordítás, amely az egyik toolchainen sikerül, a másikon lavinát indít. Ha a környező őr egy Delphi verzióellenőrzés, amelyet a Free Pascal nem teljesít, a deklaráció eltűnik az FPC számára, miközben a feltétel nélküli implementáció megmarad, és a fordító egy hosszú panaszlistát jelent azokról a metódusazonosítókról, amelyeket várt, és nem talált. Egyik üzenet sem említi az okozó feltételes blokkot
Két szokás előzi meg az egész hibaosztályt. Interfész szakaszba való beszúrás előtt nézzen felfelé a legközelebbi nyitott feltételért, a vizuális csoportosításba vetett bizalom helyett. És a zöld Delphi tesztcsomagot kezelje csak Delphi-re vonatkozó bizonyítékként: a Free Pascal könyvtárbuild külön kapu, és az egyetlen mód annak tudására, hogy átmegy, ha lefuttatja a build-Win32-Lib-FPC.cmd és build-Win64-Lib-FPC.cmd szkripteket ugyanazon változtatás részeként
Mi törik meg a 32 bites aritmetikai kódban?
Egy nyelvi korlátozás pontosan abban a kódban mutatkozik meg, amely a legkevésbé hajlandó változni: a 32 bites Free Pascal nem fogad el UInt64-et for ciklusvezérlő változóként. Az X25519-et és X448-at hordozó elliptikusgörbe unitokban a limb tömböket bejáró ciklusokat egyszerűen azért írták 64 bites számlálókkal, mert a fájlban minden más 64 bites
A javításnak sebészinek kell lennie, mert a field aritmetikában egy változó szélessége a helyességi érvelés része. A ciklusindexek Integer-ek lesznek, mert egy limb tömbnek maroknyi eleme van, és egyetlen index sem közelíti meg a 32 bites tartományt. Minden, ami az aritmetikában részt vesz, maguk a limbok, az átvitelterjedés és a maszkok, UInt64 marad, mert bármelyik szűkítése csendben megváltoztatja az eredményt a test prímje szerint
// A 32 bites FPC elutasít egy UInt64 ciklusváltozót. Csak az indexet szűkítse;
// a limbok, maszkok és átvitelek megtartják szélességüket, különben a field matematika megváltozik
var
I: Integer; // korábban 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;
Egy ilyen változtatás ellenőrzése nem lehet oda-vissza teszt. A titkosítás és visszafejtés ugyanazzal a hibás implementációval tökéletesen egyetért önmagával, ezért a known-answer vektorok itt alku tárgyát sem képezhetik: futtassa a publikált X25519 és X448 tesztvektorokat, és hasonlítsa össze a pontos kimeneti bájtokat. Ez az egyetlen ellenőrzés, amely megkülönböztet egy helyes implementációt egy önkonzisztensen rossztól, és ugyanúgy érvényes a Free Pascal deflate és AES kodekhatárokról szóló cikkben tárgyalt szimmetrikus primitívekre is
Mennyit ér egy Win32 Free Pascal build?
A gyakorlati hozam az, hogy egy 32 bites Windowst célzó Lazarus alkalmazás ugyanazt a dokumentummotort kapja, mint a Delphi társa, külön karbantartandó bináris szerződés nélkül. Ez azokra a telepítésekre számít a leginkább, amelyekről az emberek ritkán beszélnek: ipari vezérlők, pénztárterminálok és hosszú életű üzleti szoftverek, ahol a 32 bites futtatókörnyezet nem legacy választás, hanem hardverkorlát
A Win64 történet korábban jött, és a Free Pascal és Lazarus támogatás Win64-en írja le. A Win32 nem ennek újrafuttatása. A Win64-en egy hívási konvenció van, nincs névfestés, és nincsenek megkerülendő Delphi privát egészsegédek sem, tehát e cikk szinte minden eleme a 32 bites célon specifikus. A ciklusváltozó-változtatást igénylő aritmetikai unitok ugyanazok, amelyeket a Montgomery aritmetika a NIST görbéken ír le, ahol a szélességi fegyelem részletesebben ki van fejteni
Az általános tanulság az, hogy a keresztfordítói hordozhatósági munka elsősorban nem nyelvi funkciókról szól. Mindkét fordító ugyanazt az Object Pascalt fogadja el itt. Ami eltér, az az objektumfájl: hogyan írják le a szimbólumokat, mely segédrutinoktól tételezi fel, hogy a futtatókörnyezet adja őket, és mely előre lefordított objektumok vannak a linkben. A HotPDF a Free Pascal és Lazarus csomagokat a Delphi és C++Builder csomagokkal együtt szállítja a HotPDF Delphi PDF komponensben, tehát ugyanaz a forrásfa táplál minden toolchaint, ahelyett hogy fordítónként forkolna