Technický článek

Free Pascal Win32: dekorace C symbolů v HotPDF

Free Pascal na Win32 přidává automaticky úvodní podtržítko ke každému importu cdecl; external, zatímco public name exportuje řetězec, který jste napsali, znak za znakem. HotPDF musí v témže zdrojovém stromu uspokojit obě konvence, protože sestavení Delphi už dnes dodává importní deklarace, které podtržítko vykreslují ručně. Spletete-li se v té asymetrii, dostanete link chyby uvádějící symbol, který nikdo nenapsal

Rozšiřování Delphi knihovny na Free Pascal se obvykle popisuje jako problém přenositelnosti a na Win64 to většinou i je. Win32 je jiný. 32bitové x86 Windows ABI nese třicet let nashromážděných konvencí o tom, jak se C symboly píší, kdo uklízí zásobník a které kompilátorem privátní helpery si může překladová jednotka dovolit předpokládat, a každá z nich je místem, kde se dva Pascalovské kompilátory shodující se v jazyce mohou pořád neshodnout v objektovém souboru

Proč se tentýž symbol na Win64 rozliší a na Win32 selže?

Protože podtržítkový prefix je 32bitová konvence, kterou Free Pascal aplikuje na importy, ale ne na exporty. Deklarujete function deflate(...): Integer; cdecl; external; a FPC na Win32 hledá v objektovém souboru _deflate, na Win64 zase deflate. To je správné chování odpovídající tomu, co vysílá C kompilátor. Past je na druhé straně mostu: rutina označená public name 'deflate' exportuje na obou cílech přesně deflate, bez jakéhokoli prefixu

Teď přidejte historický detail, který to konkrétizuje. Sestavení Delphi už dnes deklaruje některé z těchhle vstupních bodů s podtržítkem zapsaným přímo do názvu, protože přesně to obsahují jeho vlastní objektové soubory. Nakrmte tutéž deklaraci FPC na Win32 a kompilátor ji povinně opatří prefixem znovu, takže linker pátrá po __deflate, symbolu, který nic neexportuje. Intuitivní oprava, přidat jedno podtržítko všude, rozbije importy, které už byly napsané správně

Funguje pár prefixových konstant, ne jediná. HPDFFPCZLib a HPDFFPCCodecStubs používají jeden prefix pro obyčejné C importy a druhý pro importy, které už nesou prefix z Delphi strany, a na Win64 jsou obě konstanty prázdné, takže stávající link názvy přežijí nedotčené. Dvě konstanty místo jedné je celá oprava a je zjevná teprve poté, co oddělíte importní pravidlo od exportního

Táž sada C symbolových deklarací rozlišená Free Pascalem a Delphi na Win64 a Win32: cdecl importy získají podtržítko jen na 32bitovém cíli, deklarace Delphi už napsaná s podtržítkem se stane __deflate a nelinkuje se, zatímco exporty public name zůstávají na obou architekturách literální
Jedna prefixová konstanta nemůže obsluhovat obě pravidla: obyčejné cdecl importy a importy už nesoucí podtržítko z Delphi se dekorují pod FPC na Win32 různě, takže HotPDF drží dvě a na Win64 nechá obě prázdné
// Dva prefixy, ne jeden: obyčejné C importy a importy už nesoucí
// ručně psaný prefix z Delphi se dekorují pod FPC/Win32 různě
const
{$IF DEFINED(FPC) and DEFINED(CPU32)}
  CPrefix     = '_';   // FPC tohle přidává sám pro cdecl external
  DelphiCName = '';    // už napsané s podtržítkem přímo ve zdrojáku
{$ELSE}
  CPrefix     = '';
  DelphiCName = '';
{$IFEND}

// Exportní strana: 'public name' je literální na každém cíli
procedure hpdf_codec_free(P: Pointer); cdecl;
  public name 'hpdf_codec_free';

WIN32 vám řekne architekturu, ne ABI

Tohle je chyba podmíněné kompilace s nejdelším debuggingovým ohonem a stojí za to ji říct napřímo: WIN32 a WIN64 popisují cílovou architekturu a neříkají nic o tom, které kompilátorem privátní runtime helpery existují. Free Pascal definuje oba symboly na odpovídajících Windows cílech, úplně jako Delphi. Hlídka napsaná jako {$IFDEF WIN32} kolem kódu volajícího Delphi runtime helper se proto pod FPC zkompiluje a selže až při linkování

Konkrétně spadá do téhle pasti tři rodiny kódu. Delphi trampolíny 64bitových celočíselných operací dosažitelné přes helpery System.@_ll, assembly podpůrné rutiny MSVC Win32 a importní sloty, které s nimi jdou, všechny existují pro předkompilované C objekty, jež linkuje sestavení Delphi. Free Pascal ty objekty nelinkuje, takže žádnou z té mechaniky nepotřebuje a každá reference na ni musí zmizet. Subtilita je v tom, že deklarace i implementace se musí vyloučit společně. Vyloučíte-li jen jednu, kompilátor nahlásí cosi nepoužitelného o identifikátoru, který nedokáže ničemu přiřadit

Pravidlo, které z toho padá, je krátké. Hlídejte se kompilátorem, když se otázka týká ABI nebo runtime podpory, architekturou, když se týká šířky ukazatele nebo počtu registrů, a nikdy nenechte jedno stát za druhé

Hlídky na deklaracích a implementacích společně

Podmíněný blok v sekci interface se snadno mině s povšimnutím a výsledná chybová zpráva míří kamkoli jinam než na příčinu. Přidáte-li deklaraci metody do class interface, přirozené místo pro ni je vedle příbuzných metod, což je v pořádku přesně do chvíle, kdy ti sousedé zrovna sedí uvnitř existujícího bloku {$IFDEF}. Podmíněné direktivy se neodsazují, takže blok otevřený o čtyřicet řádků výš je při čtení okolních deklarací v podstatě neviditelný

Co následuje, je kompilace, která na jednom toolchainu uspěje a na druhém vyprodukuje kaskádu. Je-li okolní hlídka kontrolou verze Delphi, kterou Free Pascal nesplňuje, deklarace zmizí pro FPC, zatímco nepodmíněná implementace zůstane, a kompilátor nahlásí dlouhý seznam stížností na identifikátory metod, které čekal a nenašel. Žádná ze zpráv nezmíní podmíněný blok, který to způsobil

Dva návyky téhle třídě selhání zabrání celé. Před vkládáním do sekce interface se podívejte vzhůru po nejbližším otevřeném podmíněném bloku, místo abyste věřili vizuálnímu seskupení. A berte zelenou testovací sadu Delphi jako důkaz jen o Delphi: build knihovny pro Free Pascal je samostatná brána a jediný způsob, jak vědět, že prochází, je spustit build-Win32-Lib-FPC.cmd a build-Win64-Lib-FPC.cmd jako součást téže změny

Co se rozbije v 32bitovém aritmetickém kódu

Jedno jazykové omezení se objeví přesně v kódu nejmenší ochotném ke změně: 32bitový Free Pascal nepřijme UInt64 jako řídící proměnnou cyklu for. V jednotkách eliptických křivek nesoucích X25519 a X448 byly smyčky procházející pole limbů psány s 64bitovými počítadly čistě proto, že všechno ostatní v souboru je 64bitové

Oprava musí být chirurgická, protože v polní aritmetice je šířka proměnné částí argumentu správnosti. Cyklické indexy se stanou Integer, neboť pole limbů má hrst prvků a žádný index se nikdy nepřiblíží 32bitovému rozsahu. Všechno, co se na aritmetice podílí, samotné limby, propagace přenosu a masky, zůstává UInt64, protože zúžení kteréhokoli z nich tiše změní výsledek modulo prvočíslo pole

// 32bitové FPC odmítá proměnnou cyklu UInt64. Zúž jen index;
// limby, masky a přenosy si nechají šířku, jinak se změní polní matika
var
  I: Integer;                 // dříve 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;

Ověření takové změny nemůže být round-trip test. Šifrování a dešifrování stejnou rozbitou implementací se samo se sobě dokonale shodne, a proto jsou known-answer vektory tady nevyjednatelné: pusťte publikované testové vektory X25519 a X448 a porovnejte přesné výstupní bajty. To je jediná kontrola odlišující správnou implementaci od sebe-si-konzistentní špatné a platí stejně pro symetrické primitivy rozebrané v článku hranice kodeků deflate a AES ve Free Pascalu

Dva lomové body buildu HotPDF Win32 Free Pascal: hlídka {$IFDEF WIN32} kolem Delphi runtime helperů, která se zkompiluje, ale selže při linku, nejsou-li deklarace a implementace vyloučeny společně, a proměnná cyklu UInt64 v limb průchodech X25519 a X448 zúžená na Integer, zatímco limby, přenosy a masky si drží šířku
Hlídejte se kompilátorem, když se ptáte na ABI nebo runtime podporu, architekturou, když na šířku ukazatele, a aritmetické změny pak dokazujte proti publikovaným known-answer vektorům, ne round-trip testy

Co je build Win32 Free Pascal platný

Praktický zisk je, že Lazarus aplikace cílená na 32bitové Windows dostane tentýž dokumentový engine jako její protějšek v Delphi, bez samostatné binární smlouvy k udržování. Nejvíc to znamená pro nasazení, o nichž se málo mluví: průmyslové řadiče, pokladní terminály a dlouhověký firemní software, kde je 32bitový runtime ne zastaralá volba, ale hardwarové omezení

Příběh Win64 přišel první a popisuje ho článek podpora Free Pascal a Lazarus na Win64. Win32 není jeho opakování. Win64 má jednu volací konvenci, žádnou dekoraci jmen a žádné celočíselné helpery privátní k Delphi, s nimiž by se muselo pracovat, takže téměř všechno v tomhle článku je specifické pro 32bitový cíl. Aritmetické jednotky, které potřebovaly změnu proměnné cyklu, jsou tytéž popsané v článku Montgomeryho aritmetika nad NIST křivkami, kde je šířková disciplína vysvětlena do hloubky

Obecné poučení zní, že práce na cross-kompilátorové přenositelnosti primárně není o jazykových vlastnostech. Oba kompilátory tady přijímají stejný Object Pascal. Liší se objektový soubor: jak se píší symboly, které podpůrné rutiny se od runtime předpokládají a které předkompilované objekty jsou v linku. HotPDF dodává balíčky Free Pascal a Lazarus vedle těch pro Delphi a C++Builder v rámci HotPDF Delphi PDF komponenty, takže stejný zdrojový strom krmí každý toolchain místo větvění per kompilátor