Odborný článok

Free Pascal Win32: dekorácia C symbolov v HotPDF

Free Pascal na Win32 pridáva automaticky úvodný podčiarknik ku každému importu cdecl; external, kým public name exportuje reťazec, ktorý ste napísali, znak za znakom. HotPDF musí uspokojiť obe konvencie v tom istom zdrojovom strome, pretože Delphi build už teraz dodáva deklarácie importov, ktoré podčiarknik vypisujú ručne. Pomýliť sa na tejto asymetrii znamená linkové chyby menujúce symbol, ktorý nenapísal nikto

Rozšírenie Delphi knižnice na Free Pascal sa zvyčajne opisuje ako problém prenositeľnosti a na Win64 to zväčša aj je. Win32 je iný. 32-bitové x86 Windows ABI nesie tridsať rokov nazbieranej konvencie o tom, ako sa píšu C symboly, kto čistí zásobník a ktoré prekladačové privátne helpery smie prekladová jednotka predpokladať, a každá z týchto vecí je miesto, kde sa dva Pascal prekladače, ktoré sa v jazyku zhodujú, môžu nezhodnúť na objektovom súbore

Prečo sa rovnaký symbol vyrieši na Win64 a zlyhá na Win32?

Pretože predpona podčiarknika je 32-bitová konvencia, ktorú Free Pascal aplikuje na importy, ale nie na exporty. Deklarujte function deflate(...): Integer; cdecl; external; a FPC hľadá _deflate v objektovom súbore na Win32 a deflate na Win64. To je správne správanie a sedí s tým, čo emituje C prekladač. Pasca je na druhej strane mosta: rutina označená public name 'deflate' exportuje presne deflate na oboch cieľoch, bez akejkoľvek pridanej predpony

Pridajte teraz historický detail, ktorý to konkrétizuje. Delphi build už deklaruje niektoré z týchto vstupných bodov s podčiarknikom zapísaným priamo do mena, pretože presne to obsahujú jeho vlastné objektové súbory. Dajte tú istú deklaráciu FPC na Win32 a prekladač ju povinne predponuje znova, takže linker pátra po __deflate, symbole, ktorý nič neexportuje. Intuitívna oprava, pridať jeden podčiarknik všade, rozbije importy, ktoré boli už napísané správne

Funguje dvojica konštánt s predponou, nie jediná. HPDFFPCZLib a HPDFFPCCodecStubs používajú jednu predponu pre obyčajné C importy a druhú pre importy, ktoré už nesú predponu z delphi strany, a na Win64 sú obe konštanty prázdne, takže existujúce linkové mená prežijú nedotknuté. Dve konštanty namiesto jednej je celá oprava a je zrejmá až vtedy, keď oddelíte pravidlo importu od pravidla exportu

Rovnaké deklarácie C symbolov vyriešené Free Pascal a Delphi na Win64 a Win32: cdecl importy dostanú podčiarknik len na 32-bitovom cieli, deklarácia Delphi už napísaná s podčiarknikom sa stane __deflate a linkovanie zlyhá, kým public name exporty zostávajú doslovné na oboch architektúrach
Jedna konštanta s predponou neobsúži obe pravidlá: obyčajné cdecl importy a importy už nesúce delphi podčiarknik sa pod FPC na Win32 dekorujú odlišne, takže HotPDF drží dve a na Win64 nechá obe prázdne
// Dve predpony, nie jedna: obyčajné C importy a importy už nesúce
// ručne písanú delphi predponu sa pod FPC/Win32 dekorujú odlišne
const
{$IF DEFINED(FPC) and DEFINED(CPU32)}
  CPrefix     = '_';   // toto si FPC pridáva sám pri cdecl external
  DelphiCName = '';    // podčiarknik už je napísaný priamo v zdrojku
{$ELSE}
  CPrefix     = '';
  DelphiCName = '';
{$IFEND}

// Strana exportu: 'public name' je doslovné na každom cieli
procedure hpdf_codec_free(P: Pointer); cdecl;
  public name 'hpdf_codec_free';

WIN32 vám povie architektúru, nie ABI

Toto je chyba podmienenej kompilácie s najdlhším ladiacim chvostom a stojí za to ju povedať na rovinu: WIN32 a WIN64 popisujú cieľovú architektúru a nič nehovoria o tom, ktoré prekladačové privátne behové helpery existujú. Free Pascal definuje oba symboly na zodpovedajúcich Windows cieľoch, úplne rovnako ako Delphi. Ochrana napísaná ako {$IFDEF WIN32} okolo kódu volajúceho behový helper Delphi preto skompiluje pod FPC a zlyhá až pri linkovaní

Konkrétne do tejto pasce padajú tri rodiny kódu. Trampolíny Delphi pre 64-bitové celé čísla dosahované cez helpery System.@_ll, podporné assemblerové rutiny MSVC pre Win32 a importné sloty, ktoré s nimi chodia, všetky existujú na obsluhu predkompilovaných C objektov, ktoré Delphi build linkuje. Free Pascal tieto objekty nelinkuje, takže žiadny z toho mechanizmu nepotrebuje a každá referencia naň musí zmiznúť. Jemnosť je v tom, že deklarácia aj implementácia sa musia vylúčiť spolu. Vylúčite len jednu a prekladač nahlási niečo nepoužiteľné o identifikátore, ktorý nedokáže spárovať s ničím

Pravidlo, ktoré z toho vypadáva, je krátke. Strážte prekladačom, keď sa otázka týka ABI alebo behovej podpory, strážte architektúrou, keď sa týka šírky pointera alebo počtu registrov, a nikdy nedovoľte, aby jedna vec zastupovala druhú

Stráženie deklarácií a implementácií spolu

Do podmieneného bloku v sekcii interface sa dá spadnúť bez všimnutia a výsledná chybová správa ukazuje hocikam, len nie na príčinu. Pridáte deklaráciu metódy do interface triedy a prirodzené miesto pre ňu je vedľa súvisiacich metód, čo je v poriadku presne do chvíle, keď tí susedia náhodou sedia vo vnútri existujúceho bloku {$IFDEF}. Podmienené direktívy sa neodsádzajú, takže blok otvorený štyridsať riadkov vyššie je pri čítaní okolitých deklarácií v podstate neviditeľný

Čo nasleduje, je kompilácia, ktorá uspeje na jednom toolchainu a vyprodukuje kaskádu na druhom. Ak je okolitá ochrana kontrolou verzie Delphi, ktorú Free Pascal nesplňa, deklarácia pre FPC zmizne, kým nepodmienená implementácia zostane, a prekladač nahlási dlhý zoznam sťažností na identifikátory metód, ktoré očakával a nenašiel. Ani jedna správa nespomína podmienený blok, ktorý to spôsobil

Dva návyky zabránia celej tejto triede zlyhaní. Pred vkladaním do sekcie interface sa pozrite nahor po najbližšom otvorenom podmienenom riadku namiesto dôvery vizuálnemu zoskupeniu. A zelenú testovaciu sadu Delphi berte ako dôkaz len o Delphi: zostavenie knižnice Free Pascal je samostatná brána a jediný spôsob, ako vedieť, že prechádza, je spustiť build-Win32-Lib-FPC.cmd a build-Win64-Lib-FPC.cmd ako súčasť tej istej zmeny

Čo sa rozbije v 32-bitovom aritmetickom kóde

Jedno jazykové obmedzenie sa objaví presne v kóde, ktorý meniť najmenej chce: 32-bitový Free Pascal neprijme UInt64 ako riadiacu premennú for cyklu. V unitách eliptických kriviek nesúcich X25519 a X448 boli cykly prechádzajúce polia limb napísané s 64-bitovými počítadlami jednoducho preto, že všetko ostatné v súbore je 64-bitové

Oprava musí byť chirurgická, pretože v telesnej aritmetike je šírka premennej súčasťou argumentu o správnosti. Indexy cyklov sa stanú Integer, keďže pole limb má hrstku prvkov a žiadny index sa nikdy nepriblíži k 32-bitovému rozsahu. Všetko, čo sa zúčastňuje aritmetiky, samotné limb, šírenie prenosu aj masky, zostáva UInt64, pretože zúženie ktoréhokoľvek z nich poticho zmení výsledok modulo prvočíslo telesa

// 32-bitový FPC odmietne premennú cyklu UInt64. Zúžte len index;
// limb, masky a prenosy si nechajú šírku, inak sa zmení telesová matematika
var
  I: Integer;                 // pôvodne 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;

Overenie takejto zmeny nemôže byť round-trip test. Šifrovanie a dešifrovanie tou istou pokazenou implementáciou sa samo so sebou dokonale zhoduje, a práve preto sú tu known-answer vektory nevyhnutné: spustite publikované testovacie vektory X25519 a X448 a porovnajte presné výstupné bajty. To je jediná kontrola, ktorá odlíši správnu implementáciu od sebakonzistentnej nesprávnej, a rovnako sa vzťahuje na symetrické primitívy rozobrané v článku hranice kodekov deflate a AES pre Free Pascal

Dva body zlyhania buildu HotPDF Win32 Free Pascal: ochrana {$IFDEF WIN32} okolo behových helperov Delphi, ktorá skompiluje, ale zlyhá pri linke, ak sa deklarácia a implementácia nevylúčia spolu, a premenná cyklu UInt64 v prechadoch limb X25519 a X448 zúžená na Integer, kým limb, prenosy a masky si zachovajú šírku
Strážte prekladačom, keď sa otázka týka ABI alebo behovej podpory, a architektúrou, keď sa týka šírky pointera, potom dokážte aritmetické zmeny proti publikovaným known-answer vektorom, nie round-trip testom

Čo je build Win32 Free Pascal hodný

Praktický zisk je ten, že Lazarus aplikácia cieliaca na 32-bitové Windows dostane ten istý dokumentový engine ako jej delphi náprotivok, bez osobitnej binárnej zmluvy na údržbu. Najviac to záleží pri nasadeniach, o ktorých sa málo hovorí: priemyselné radiče, pokladničné terminály a dlhoveký podnikový softvér, kde 32-bitové behové prostredie nie je legacy voľba, ale hardvérové obmedzenie

Príbeh Win64 prišiel najprv a popisuje ho článok podpora Free Pascal a Lazarus na Win64. Win32 nie je jeho opakovanie. Win64 má jednu konvenciu volania, žiadnu dekoráciu mien a žiadne delphi privátne celočíselné helpery na obídenie, takže skoro všetko v tomto článku je špecifické pre 32-bitový cieľ. Aritmetické unitá, ktoré potrebovali zmenu premennej cyklu, sú tie isté, ktoré popisuje článok Montgomery aritmetika nad NIST krivkami, kde je šírková disciplína vysvetlená hlbšie

Všeobecné ponaučenie znie, že práca na prenositeľnosti medzi prekladačmi nie je v prvom rade o jazykových vlastnostiach. Oba prekladače tu akceptujú ten istý Object Pascal. Iný je objektový súbor: ako sa píšu symboly, ktoré pomocné rutiny má behové prostredie poskytovať a ktoré predkompilované objekty sú v linke. HotPDF dodáva balíky Free Pascal a Lazarus vedľa tých pre Delphi a C++Builder v HotPDF Delphi PDF komponente, takže ten istý zdrojový strom živí každý toolchain, namiesto aby sa rozvetvil podľa prekladača