Free Pascal på Win32 lägger till en inledande understrykning på varje cdecl; external-import automatiskt, medan public name exporterar strängen du skrev, tecken för tecken. HotPDF måste tillfredsställa båda konventionerna i samma källträd, för Delphi-bygget skickar redan importdeklarationer som stavar ut understrykningen för hand. Att få den asymmetrin fel ger länkfel som namnger en symbol ingen skrev
Att utvidga ett Delphi-bibliotek till Free Pascal beskrivs vanligen som ett portabilitetsproblem, och på Win64 är det mest det. Win32 är annorlunda. 32-bitars x86-Windows-ABI:n bär på trettio års ansamlad konvention om hur C-symboler stavas, vem som städar stacken och vilka kompilatorprivata hjälprutiner en översättningsenhet får förutsätta, och vart och ett av dem är en plats där två Pascal-kompilatorer som är överens om språket ändå kan vara oense om objektfilen
Varför löser sig samma symbol på Win64 och fallerar på Win32?
Därför att understrykningsprefixet är en 32-bit-konvention som Free Pascal tillämpar på importer men inte på exporter. Deklarera function deflate(...): Integer; cdecl; external; och FPC letar efter _deflate i objektfilen på Win32, och efter deflate på Win64. Det är korrekt beteende och matchar vad en C-kompilator avger. Fällan sitter på andra sidan bron: en rutin märkt public name 'deflate' exporterar precis deflate på båda målen, utan tillagt prefix
Lägg nu till den historiska detalj som gör allt konkret. Delphi-bygget deklarerar redan några av dessa ingångspunkter med understrykningen skriven i namnet, för det är vad dess egna objektfiler innehåller. Mata samma deklaration till FPC på Win32 och kompilatorn plikttroget prefixar den igen, så länkaren jagar __deflate, en symbol inget exporterar. Den intuitiva fixen, att lägga till en understrykning överallt, bryter importerna som redan stavats rätt
Det som fungerar är ett par prefixkonstanter i stället för en enda. HPDFFPCZLib och HPDFFPCCodecStubs använder ett prefix för vanliga C-importer och ett annat för importer som redan bär ett Delphi-sidigt prefix, och på Win64 är båda konstanterna tomma så att befintliga länknamn överlever orörda. Två konstanter i stället för en är hela fixen, och den är bara uppenbar när du väl separerat importregeln från exportregeln
// Två prefix, inte ett: vanliga C-importer och importer som redan bär
// ett handskrivet Delphi-prefix dekoreras olika under FPC/Win32
const
{$IF DEFINED(FPC) and DEFINED(CPU32)}
CPrefix = '_'; // FPC lägger till detta själv för cdecl external
DelphiCName = ''; // redan stavad med understrykningen i källkoden
{$ELSE}
CPrefix = '';
DelphiCName = '';
{$IFEND}
// Exportsidan: 'public name' är literal på varje mål
procedure hpdf_codec_free(P: Pointer); cdecl;
public name 'hpdf_codec_free';
WIN32 berättar arkitekturen, inte ABI:n
Det här är villkorlig-kompileringsmisstaget med den längsta felsökningssvansen, och det är värt att sägas rakt ut: WIN32 och WIN64 beskriver målarkitekturen och säger ingenting om vilka kompilatorprivata runtime-hjälpare som finns. Free Pascal definierar båda symbolerna på motsvarande Windows-mål, precis som Delphi gör. En vakt skriven som {$IFDEF WIN32} runt kod som anropar en Delphi-runtime-hjälpare kompilerar därför under FPC och fallerar vid länkningen
Konkret är det tre familjer av kod som trillar i den fällan. Delphis 64-bit heltalstrampoliner nådda via System.@_ll-hjälparna, MSVC:s Win32-assemblerstödsrutiner och de importplatser som hör till dem existerar alla för att tjäna förkompilerade C-objekt som Delphi-bygget länkar in. Free Pascal länkar inte de objekten, så det behöver ingen av den maskineriet, och varje referens till den måste försvinna. Den subtila detaljen är att både deklarationen och implementationen måste uteslutas tillsammans. Uteslut bara en och kompilatorn rapporterar något oanvändbart om en identifierare den inte kan matcha mot någonting
Regeln som faller ut ur det är kort. Lägg villkoret på kompilatorn när frågan gäller ABI eller runtime-stöd, och på arkitekturen när frågan gäller pekarbredd eller registerantal, och låt aldrig den ena stå för den andra
Att gardera deklarationer och implementationer tillsammans
Ett villkorsblock i interface-sektionen är lätt att trilla in i utan att märka det, och det resulterande felmeddelandet pekar var som helst utom på orsaken. Lägg till en metoddeklaration i ett klassinterface och den naturliga platsen är bredvid de relaterade metoderna, vilket är fint ända till ögonblicket de grannarna råkar sitta inuti ett befintligt {$IFDEF}-block. Villkorsdirektiv indenteras inte, så ett block som öppnades fyrtio rader upp är i praktiken osynligt medan du läser de omgivande deklarationerna
Det som händer sedan är en kompilering som lyckas på en verktygskedja och ger en kaskad på en annan. Om den omgivande vakten är en Delphi-versionskontroll som Free Pascal inte uppfyller försvinner deklarationen för FPC medan den ovillkorade implementationen blir kvar, och kompilatorn rapporterar en lång lista klagomål om metodidentifierare den förväntade sig och inte fann. Inget av meddelandena nämner det villkorsblock som orsakade det
Två vanor förebygger hela fellivet. Innan du infogar i en interface-sektion, titta uppåt efter närmast öppna villkor i stället för att lita på den visuella grupperingen. Och behandla en grön Delphi-testsvit som bevis endast om Delphi: Free Pascal-biblioteksbygget är en separat grind, och enda sättet att veta att den går igenom är att köra build-Win32-Lib-FPC.cmd och build-Win64-Lib-FPC.cmd som del av samma ändring
Vad som går sönder i 32-bit aritmetikkod
En språkbegränsning dyker upp i exakt den kod som är minst villig att ändras: 32-bit Free Pascal accepterar inte en UInt64 som styrvariabel i en for-loop. I de elliptiska kurvenheterna som bär X25519 och X448 var looparna som vandrar igenom limb-arrayer skrivna med 64-bit räknare av den enkla anledningen att allt annat i filen är 64-bit
Fixen måste vara kirurgisk, för i field-aritmetiken är variabelbredden del av korrekthetsargumentet. Loopindex blir Integer, eftersom en limb-array har en handfull element och inget index närmar sig 32-bit-intervallet. Allt som deltar i aritmetiken, limbbs själva, carry-propageringen och maskerna, stannar UInt64, för att göra någon av dem smalare ändrar tyst resultatet modulo primtalet i kroppen
// 32-bit FPC avvisar en UInt64-loopvariabel. Gör bara indexet smalare;
// limbs, masker och carries behåller sin bredd, annars ändras matematiken
var
I: Integer; // var 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;
Verifieringen för en sådan ändring kan inte vara ett roundtrip-test. Att kryptera och dekryptera med samma söndriga implementation överensstämmer perfekt med sig själv, vilket är varför kända-svar-vektorer inte är förhandlingsbara här: kör de publicerade X25519- och X448-testvektorerna och jämför exakta utdatabyte. Det är den enda kontroll som skiljer en korrekt implementation från en självkonsistent felaktig, och den gäller lika för de symmetriska primitiverna som diskuteras i Free Pascal-gränserna för deflate- och AES-codecs
Vad ett Win32 Free Pascal-bygge är värt
Den praktiska utdelningen är att en Lazarus-applikation riktad mot 32-bit Windows får samma dokumentmotor som sin Delphi-motsvarighet, utan ett separat binärt kontrakt att underhålla. Det spelar störst roll för de deploymenter folk sällan pratar om: industriella styrsystem, point-of-sale-terminaler och långlivad affärssystemprogramvara där 32-bit-runtimen inte är ett legacy-val utan en hårdvarubegränsning
Win64-historien kom först och beskrivs i Free Pascal- och Lazarus-stöd på Win64. Win32 är inte en repris av den. Win64 har en anropskonvention, ingen namndekorering och inga Delphi-privata heltalshjälpare att jobba runt, så nästan allt i den här artikeln är specifikt för 32-bit-målet. Aritmetikenheterna som behövde loopvariabeländringen är samma som beskrivs i Montgomery-aritmetik över NIST-kurvor, där breddsdisciplinen förklaras mer på djupet
Den allmänna lärdomen är att arbete med korskompilatorportabilitet i första hand inte handlar om språkdrag. Båda kompilatorerna accepterar samma Object Pascal här. Det som skiljer är objektfilen: hur symboler stavas, vilka hjälprutiner runtimen förväntas tillhandahålla och vilka förkompilerade objekt som är med i länken. HotPDF skickar Free Pascal- och Lazarus-paketen vid sidan av Delphi- och C++Builder-paketen i HotPDF Delphi PDF component, så samma källträd matar varje verktygskedja i stället för att forka per kompilator