PDFlibPas bouwt onder Free Pascal voor 32-bit Windows, en het moeilijke stuk was nooit de Pascal. Het waren de objectbestanden: de AES- en OpenJPEG-objecten waarmee de Delphi-build linkt zijn OMF, de interne linker van Free Pascal eist COFF, en het converteren tussen beide levert sectienamen en sectiedefinities-symbolen op waar de linker op afhaakt met internal errors in plaats van een bruikbare diagnose
Wie ooit C-objecten in een Pascallibrary heeft gelinkt, kent dit terrein. Win64 is betrekkelijk beschaafd: één objectformaat, één calling convention en geen name decoration. Win32 bewaart elke historische laag die het platform heeft opgestapeld, en een library die third-party C-code statisch linkt krijgt ze allemaal tegelijk voor de voeten
De compilermap vertelt u niet wat de target is
Begin bij het entry point van de build, want hier een fout maken kost uren voordat er überhaupt een objectbestand meedoet. Een directorynaam van een Free Pascal-installatie zegt waar de hoofdcompiler staat, niet wat die produceert. Een 32-bit hostcompiler kan een cross-compiler aanroepen die ernaast staat en 64-bit code uitspugen zodra u de juiste target-switches meegeeft, dus het target uit een pad afleiden is gokken dat toevallig werkt totdat iemand zijn toolchain herschikt
De betrouwbare aanpak is de compiler het zelf te vragen. Vraag de werkelijke target-processor en het besturingssysteem op via de informatieswitches van de compiler zelf, en accepteer beide gangbare installatielayouts, de vlakke binaire directory en de in versies geneste, want verschillende installers en toolchainmanagers leveren verschillende vormen op. Een buildscript dat één van die layouts hardcodet, werkt op precies één machine
Waarom breekt een geconverteerd objectbestand de interne linker?
Omdat de conversie de OMF-sectienaamconventie overeind houdt en sectiedefinities-symbolen aanmaakt die niet matchen met wat de COFF-linker verwacht. De OMF-objecten naar COFF converteren is nodig maar niet voldoende: de resulterende bestanden dragen de klassieke _TEXT-, _DATA- en _BSS-sectienamen, plus daarvan afgeleide namen voor sectiedefinities-symbolen, en dat aan de interne linker van Free Pascal voeren levert internal compiler errors op in plaats van een melding over sectienamen
Een internal error is de slechtste faalmodus voor een buildprobleem, want het vertelt met geen woord wat er aan de invoer mis was. De oplossing is een normalisatieslag over het COFF-bestand na de conversie: herschrijf de sectienamen naar de verwachte vorm en herschrijf de bijbehorende sectiedefinities-symbolen mee, terwijl de symboolindex, de codebytes en de relocations onaangetast blijven. Die laatste beperking is de hele moeilijkheid. Een herschrijving die symbolen hernummert of offsets verschuift, levert een object op dat linkt en daarna crasht
Voor één van de twee objectsets is er nog een voorafgaande stap. De OpenJPEG-objecten die de klassieke 32-bit C++-compiler bouwt, zijn afhankelijk van private Delphi-routines voor 64-bit integer-rekenwerk, en Free Pascal levert die niet, dus geen enkele formaatconversie maakt ze bruikbaar. Die worden eerst herbouwd met de op Clang gebaseerde compiler, die die afhankelijkheden niet uitzendt, en daarna geconverteerd
// Objecten voor het FPC-target staan in hun eigen directory. Ze vervangen
// de Delphi-objectset niet, want beide toolchains bouwen uit dezelfde
// source tree en elk heeft zijn eigen link-invoer nodig
//
// Lib\thirdparty\Win32 Delphi OMF-objecten, ongewijzigd
// Lib\thirdparty\Win32f FPC COFF-objecten, geconverteerd en genormaliseerd
//
// Build-entrypunten:
// build-Win32-Lib-FPC.cmd
// build-Win64-Lib-FPC.cmd
Compilerprivate helpers zijn niet portabel, en hun conventies al evenmin
De Delphi-runtime levert assembly-trampolines voor 64-bit integer-bewerkingen op 32-bit x86, en voorgecompileerde C-objecten die voor Delphi zijn gebouwd roepen die aan. Free Pascal heeft zijn eigen regeling, dus die verwijzingen moeten anders ingevuld worden in plaats van omgeleid. Het detail dat omleiden onmogelijk maakt, is de calling convention: de timing-helper die de imaging-code gebruikt, laat zijn vier-byte argument door de callee opschonen, terwijl de 64-bit delingshelper zestien bytes opschoont en zijn resultaat teruggeeft in het klassieke registerpaar. Twee helpers, twee conventies, en een trampoline die voor de een geschreven is, corrupteert stilletjes de stack voor de andere
Name decoration vormt de tweede helft van het probleem. Op Win32 zet Free Pascal automatisch een underscore voor externe C-imports, terwijl public name-declaraties letterlijk worden geëxporteerd, dus de importkant en de exportkant van dezelfde bridge volgen verschillende regels. De C-runtime-bridge die OpenJPEG nodig heeft, moet daarom precies de C-symbolnamen exporteren, en de variadic-entrypunten hebben een 32-bit indirecte sprong nodig in plaats van een directe. Niets hiervan is exotisch zodra het er staat. Het hele verhaal faalt als een link-error die een symbool noemt dat niemand geschreven heeft
Waarom stierf een Win32-executable vóór main?
Een 64-bit DLL op het zoekpad, bereikt omdat de Free Pascal-unit zlib dynamisch bindt in plaats van statisch te linken. Het symptoom was een onmiddellijke exit met de invalid-image-statuscode, voordat er ook maar één regel Pascal in het programma draaide, wat u naar het programma laat zoeken dat u net bouwde terwijl de fout zit in de loader die een import tegen de verkeerde architectuur resolved
De les gaat over aannames, niet over zlib. Een unit die naar een compressielibrary is vernoemd, bevat er niet noodzakelijk één; het kan net zo goed een binding zijn die op run time een shared library verwacht, en een dynamische afhankelijkheid die u niet bedoelde is een deployment-risico, ook als hij toevallig resolved. Overschakelen naar de pure Pascal stream-implementatie geeft beide targets een statisch ingebouwde compressieroute zonder enige externe afhankelijkheid, en dat is precies wat een library die in iemand anders applicatie belandt vanaf het begin zou moeten hebben
Hetzelfde instinct geldt voor de externe JBIG2 encoder backend. Op het 32-bit target wordt de externe encoder niet gelinkt, dus verzoeken vallen terug op de ingebouwde Pascal-encoder, en de test die dat verifieert moet de registratiestatus van het huidige target controleren in plaats van een geslaagde encode te zien als bewijs dat de externe backend aanwezig is. Een fallback die werkt is precies het ding dat een ontbrekende afhankelijkheid verhult, en dat is het faalpatroon dat in de diagnostiek van stille stub-fouten wordt behandeld. Het 64-bit statische linkwerk staat beschreven in jbig2enc statisch linken onder FPC
32-bit rekenwerk op een memory stream
Code die buffergroottes met pointerbrede unsigned-rekenkunde bewerkt, is correct op Win64 en op Win32 één groot beeld van een overflow verwijderd. De in-memory stream die de JPEG 2000 codec voedt, groeit door verdubbelingen en schuift op door optellingen, en op een 32-bit target kunnen beide bewerkingen omslaan op invoer die groot maar volstrekt legitiem is
Elke write, skip, seek en initiële allocatie controleert daarom eerst en rekent pas daarna, en het capaciteitsplafond is de maximale signed pointerbreedte-waarde, gekozen om te matchen met wat de block-move-routine en de callback-returnwaarden kunnen uitdrukken. De gedragseis bij een geweigerd verzoek is makkelijk fout te doen: weigeren mag de streampositie noch de lengte veranderen. Een gedeeltelijke mutatie gevolgd door een fout laat de stream achter in een toestand waar de aanroeper niet meer over kan redeneren, en de volgende bewerking maakt het alleen maar erger
// Eerst controleren, dan rekenen. Op Win32 slaan beide controles om op
// invoer die een grote JPEG 2000 afbeelding volkomen legitiem oplevert
if Needed > NativeUInt(High(NativeInt)) - FPosition then
Exit(False); // weigeren, positie en grootte met rust laten
NewCapacity := FCapacity;
while (NewCapacity < FPosition + Needed) do
begin
if NewCapacity > NativeUInt(High(NativeInt)) shr 1 then
Exit(False); // verdubbelen zou overflowen
NewCapacity := NewCapacity shl 1;
end;
Twee build-output valkuilen die de port overleven
Test- en sample-executables per target-architectuur in eigen output-directory's scheiden is onmiskenbaar juist en breekt onmiddellijk alles wat zijn testdata vond door directoryniveaus omhoog te tellen. De oplossing is omhoog te zoeken naar de asset-directory in plaats van een vaste diepte aan te nemen, met één bewuste beperking: het signing-sample accepteert een certificaatfallback alleen uit zijn eigen projectdirectory, nooit uit een willekeurige voorouder, want een certificaat met dezelfde naam hoger in de boom is een security-verrassing in plaats van een gemak
De tweede valkuil overleeft elke port en is het meenemen naar elk FPC-project waard. Na een compilerupgrade is de compiler verouderde PPU-bestanden laten weigeren niet genoeg, want de linker geeft nog steeds de voorkeur aan achtergebleven objectbestanden in het unit-zoekpad, zelfs als de geladen PPU uit de juiste directory kwam, en een expliciet object-outputpad toevoegen zet die voorkeur niet buiten werking. Het enige betrouwbare antwoord is per buildronde een frisse tijdelijke unit-directory. Alles wat minder is, levert een binaire file op die uit twee compilerversies gelinkt is, en die faalt op manieren die op source-bugs lijken
Platformconditionals vormen het laatste stuk, en de juiste as kiezen is belangrijker dan het lijkt. De juiste vraag is meestal of de code Windows-specifiek is, niet of een bepaalde widgetlibrary aanwezig is, zoals het metafile-conversiewerk in EMF vector import en platformconditionals liet zien: die guard van een control-library-voorwaarde naar een platformvoorwaarde verschuiven veranderde een vermeende herbouw in het wijzigen van één directive. Free Pascal- en Lazarus-ondersteuning voor beide Windows-targets wordt meegeleverd met de PDFlibPas Delphi PDF library, gebouwd uit dezelfde sources als de Delphi- en C++Builder-pakketten