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
// 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
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