Tehnički članak

Free Pascal Win32: dekoracija C simbola u HotPDF-u

Free Pascal na Win32 dodaje početnu podvlaku svakom cdecl; external importu automatski, dok public name izvozi string koji ste napisali, znak po znak. HotPDF mora zadovoljiti obje konvencije u istom stablu izvora, jer Delphi build već isporučuje import deklaracije koje podvlaku izriču rukom. Pogriješite li u ovoj asimetriji, dobivate link greške koje imenuju simbol koji nitko nije napisao

Proširenje Delphi biblioteke na Free Pascal obično se opisuje kao problem prenosivosti, i na Win64 to većinom i jest. Win32 je drugačiji. 32-bitni x86 Windows ABI nosi trideset godina nakupljenih konvencija o tome kako se C simboli pišu, tko čisti stog, i koje kompajlerski privatne helper rutine smije pretpostaviti translation unit, i svako od toga je mjesto gdje se dva Pascal kompajlera koja se slažu o jeziku mogu i dalje ne slagati o objektnoj datoteci

Zašto se isti simbol razriješi na Win64 a pade na Win32?

Zato je prefiks podvlake 32-bitna konvencija koju Free Pascal primjenjuje na importe, a ne na izvoze. Deklarirajte function deflate(...): Integer; cdecl; external; i FPC traži _deflate u objektnoj datoteci na Win32, a deflate na Win64. To je ispravno ponašanje i poklapa se s onim što C kompajler emitira. Zamka je na drugoj strani mosta: rutina označena s public name 'deflate' izvozi točno deflate na oba cilja, bez dodanog prefiksa

Sada dodajte povijesni detalj koji to čini konkretnim. Delphi build već deklarira neke od tih ulaznih točki s podvlakom upisanom u ime, jer to je ono što njegove vlastite objektne datoteke sadrže. Dajte istu deklaraciju FPC-u na Win32 i kompajler ju pažljivo ponovno prefiksira, pa linker traži __deflate, simbol koji ništa ne izvozi. Intuitivni popravak, dodati jednu podvlaku posvuda, slama importe koji su već bili ispravno ispisani

Ono što radi je par prefiks konstanti umjesto jedne. HPDFFPCZLib i HPDFFPCCodecStubs koriste jedan prefiks za obične C importe i drugi za importe koji već nose Delphi stranu prefiksa, a na Win64 obje konstante su prazne pa postojeći link nazivi prežive netaknuti. Dvije konstante umjesto jedne je cijeli popravak, i jedino je očigledan kad ste odvojili import pravilo od export pravila

Iste deklaracije C simbola razriješene od Free Pascala i Delphija na Win64 i Win32: cdecl importi dobivaju podvlaku samo na 32-bitnom cilju, Delphi deklaracija već ispisana s podvlakom postaje __deflate i ne može se linkati, dok public name izvozi ostaju doslovni na obje arhitekture
Jedna prefiks konstanta ne može poslužiti oba pravila: obični cdecl importi i importi koji već nose Delphi podvlaku dekoriraju se različito pod FPC-om na Win32, pa HotPDF drži dvije i ostavlja obje prazne na Win64
// Dvije konstante, ne jedna: obični C importi i importi koji već nose
// ručno napisan Delphi prefiks dekoriraju se različito pod FPC/Win32
const
{$IF DEFINED(FPC) and DEFINED(CPU32)}
  CPrefix     = '_';   // FPC ovo dodaje sam za cdecl external
  DelphiCName = '';    // već ispisano s podvlakom u izvoru
{$ELSE}
  CPrefix     = '';
  DelphiCName = '';
{$IFEND}

// Export strana: 'public name' je doslovan na svakom cilju
procedure hpdf_codec_free(P: Pointer); cdecl;
  public name 'hpdf_codec_free';

WIN32 vam kaže arhitekturu, a ne ABI

Ovo je greška uvjetne kompilacije s najdužim repom debugiranja, i vrijedi je izreći jasno: WIN32 i WIN64 opisuju ciljnu arhitekturu i ne kažu ništa o tome koje kompajlerski privatne runtime helper rutine postoje. Free Pascal definira oba simbola na odgovarajućim Windows ciljevima, točno kao i Delphi. Guard napisan kao {$IFDEF WIN32} oko koda koji zove Delphi runtime helper stoga se kompajlira pod FPC-om i pade u trenutku linkanja

Konkretno, tri familije koda upadaju u ovu zamku. Delphi 64-bit integer trampoline dohvaćeni kroz System.@_ll helper rutine, MSVC Win32 asemblerske podrške rutine, i import slotovi koji uz njih idu svi postoje da bi služili prekompajliranim C objektima koje Delphi build linka. Free Pascal te objekte ne linka, pa ne treba nijedan dio te mašinerije, i svaka referenca na nju mora nestati. Suptilnost je da i deklaracija i implementacija moraju biti isključene zajedno. Isključite li samo jednu, kompajler javlja nešto beskorisno o identifikatoru kojeg ne može upariti ni s čime

Pravilo koje iz toga pada je kratko. Guardirajte na kompajler kad je pitanje o ABI-ju ili runtime podršci, guardirajte na arhitekturu kad je pitanje o širini pokazivača ili broju registara, i nikad ne dopustite da jedno stoji umjesto drugoga

Guardirati deklaracije i implementacije zajedno

U uvjetni blok u sekciji interface lako se upadne a da se ne primijeti, i prateća greška pokazuje bilo kamo, samo ne na uzrok. Dodate li deklaraciju metode u class interface, prirodno mjesto za nju je pokraj srodnih metoda, i to je u redu sve do trenutka kada se ti susjedi slučajno nađu unutar postojećeg {$IFDEF} bloka. Uvjetne direktive se ne uvlače, pa je blok otvoren četrdeset linija iznad bitno nevidljiv dok čitate okolne deklaracije

Ono što slijedi je kompilacija koja uspijeva na jednom toolchainu i proizvodi kaskadu na drugome. Ako je okolni guard Delphi provjera verzije koju Free Pascal ne zadovoljava, deklaracija nestane za FPC dok bezuvjetna implementacija ostaje, i kompajler javlja dug popis prigovora o identifikatorima metoda koje je očekivao i nije našao. Nijedna poruka ne spominje uvjetni blok koji je to uzrokovao

Dvije navike sprječavaju cijelu klasu pada. Prije umetanja u interface sekciju, pogledajte prema gore najbliži otvoreni uvjet umjesto da vjerujete vizualnom grupiranju. I tretirajte zeleni Delphi test suite kao dokaz samo o Delphiju: build Free Pascal biblioteke je odvojeni gate, i jedini način da znate da prolazi je pokrenuti build-Win32-Lib-FPC.cmd i build-Win64-Lib-FPC.cmd kao dio iste izmjene

Što puca u 32-bitnom aritmetičkom kodu

Jedno jezično ograničenje pojavljuje se točno u kodu najmanje sklona promjeni: 32-bitni Free Pascal neće prihvatiti UInt64 kao kontrolnu varijablu for petlje. U unitima eliptičkih krivulja koje nose X25519 i X448, petlje koje prolaze limb arrayove pisane su s 64-bit brojačima jednostavno zato što je sve ostalo u datoteci 64-bitno

Popravak mora biti kirurški, jer je u field aritmetici širina varijable dio argumenta ispravnosti. Indeksi petlji postaju Integer, jer limb array ima šaku elemenata i nijedan indeks nikad ne priđe 32-bitnom rasponu. Sve što sudjeluje u aritmetici, sami limbovi, propagacija prijenosa i maske, ostaje UInt64, jer suzavanje bilo čega od toga tiho mijenja rezultat modulo prime broja polja

// 32-bitni FPC odbija UInt64 varijablu petlje. Suzite samo indeks;
// limbovi, maske i prijenosi zadržavaju širinu ili se field matematika mijenja
var
  I: Integer;                 // bilo 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;

Verifikacija za ovakvu izmjenu ne može biti round-trip test. Šifriranje i dešifriranje istom slomljenom implementacijom savršeno se slaže samo sa sobom, i zato su known-answer vektori ovdje nepregovarivi: pokrenite objavljene X25519 i X448 test vektore i usporedite točne izlazne bajtove. To je jedina provjera koja razlikuje ispravnu implementaciju od samokonzistentno pogrešne, i vrijedi jednako za simetrične primitive obrađene u Free Pascal deflate i AES codec granicama

Dvije točke pada HotPDF Win32 Free Pascal builda: {$IFDEF WIN32} guard oko Delphi runtime helpera koji se kompajlira ali pada kod linkanja osim ako se deklaracija i implementacija isključe zajedno, i UInt64 varijabla petlje u X25519 i X448 limb prolazima sužena na Integer dok limbovi, prijenosi i maske zadržavaju svoju širinu
Guardirajte na kompajler kad je pitanje ABI ili runtime podrška, a na arhitekturu kad je širina pokazivača, pa aritmetičke izmjene dokažite protiv objavljenih known-answer vektora umjesto round-trip testova

Što vrijedi Win32 Free Pascal build

Praktična korist je da Lazarus aplikacija ciljajući 32-bitni Windows dobiva isti dokumentni engine kao njezina Delphi pandan, bez odvojenog binarnog ugovora za održavanje. Najviše je to bitno za deploymente o kojima ljudi rijetko govore: industrijske kontrolere, point-of-sale terminale i dugovječan line-of-business softver gdje 32-bitni runtime nije legacy izbor nego hardversko ograničenje

Win64 priča je došla prva i opisana je u Free Pascal i Lazarus podršci na Win64. Win32 nije njezino ponavljanje. Win64 ima jednu calling convention, nema name decoration i nema Delphi-privatnih integer helpera kojima treba zaobići, pa je gotovo sve u ovom članku specifično za 32-bitni cilj. Aritmetički uniti kojima je trebala izmjena varijable petlje su isti oni opisani u Montgomery aritmetici nad NIST krivuljama, gdje je disciplina širine objašnjena dublje

Opća lekcija je da rad na cross-compiler prenosivosti prvenstveno nije o jezičnim mogućnostima. Oba kompajlera ovdje prihvaćaju isti Object Pascal. Razlikuje se objektna datoteka: kako se simboli pišu, koje helper rutine runtime smije pretpostaviti, i koji se prekompajlirani objekti nalaze u linku. HotPDF isporučuje Free Pascal i Lazarus pakete uz Delphi i C++Builder pakete u HotPDF Delphi PDF komponenti, pa isto stablo izvora hrani svaki toolchain umjesto granjanja po kompajleru