PDFlibPas zet enhanced metafiles record voor record om in echte PDF-pagina-inhoud, in plaats van ze te rasteriseren, en juist daardoor blijft een geïmporteerde grafiek of CAD-tekening op elke zoomfactor scherp. Die converter telt zo'n 6500 regels en was geschreven tegen de VCL, dus toen de bibliotheek een Free Pascal-doel kreeg, werd die als onporteerbaar ingedeeld en door een stub vervangen. Die indeling was fout, en de manier waarop die fout was, is een nuttige les over hoe u een afhankelijkheid controleert voordat u besluit er een herschrijving omheen te bouwen
Het werkelijke VCL-oppervlak van die 6500 regels bleek klein: een bitmapklasse die gebruikt werd om zijn pixelformaat, het wegschrijven naar een stream, de handle, de canvas en de scanlines; een metafileklasse die gebruikt werd om zijn breedte, hoogte en handle; en het kleurtype met twee constanten. Elk daarvan werd al geleverd door de eigen graphics-unit van de bibliotheek, die er precies voor bestaat zodat de non-VCL-build equivalenten heeft. De converter liep helemaal niet vast op de VCL. Die liep vast op de Windows-unit van Free Pascal
Splits op de as waar de code echt van afhangt
De wijziging was dus geen herimplementatie. Het was één conditional: van "compileer de stub bij het bouwen zonder de VCL" naar "compileer de stub bij het bouwen voor niet-Windows". Dat is de juiste as, en het benoemen van het waarom maakt het verschil duidelijk. Een enhanced metafile is een Windows-container. De converter is van boven tot onder een parser voor Windows GDI-records. Of de hostapplicatie de VCL gebruikt, een andere widgetset of helemaal geen widgetset heeft niets te maken met de vraag of die records geïnterpreteerd kunnen worden; of het doel Windows is heeft er alles mee te maken
De gevolgen van het kiezen van de juiste as komen gratis mee. C++Builder-builds, die in deze bibliotheek het Windows-platformsymbool undefiniëren, behouden de stub die een exceptie gooit en gedragen zich precies als voorheen. macOS houdt de stub, terecht, want daar zijn geen GDI-records te parsen. Delphi VCL-builds blijven onaangetast. En een Windows-build met een non-VCL-widgetset krijgt vector-EMF-import als bijeffect, zonder dat iemand die hoefde te implementeren. Een conditional die met de echte afhankelijkheid strookt, maakt platformwerk tot een wijziging van één regel; een conditional op de verkeerde as maakt er een herschrijving van die nooit ingepland wordt
Het gat in Free Pascal zat in declaraties, niet in logica
Wat er werkelijk ontbrak, waren de Win32-declaraties die de Windows-unit van Delphi wel levert en die van Free Pascal niet. Ze verzamelen in één compatibiliteitsunit in plaats van conditionals door de converter te strooien hield de parser leesbaar. De lijst is leerzaam omdat die laat zien hoe ongelijkmatig de headerdekking tussen de twee RTLs is: 113 typeconstanten voor metafile-records, twee vlaggen voor extended text output, drie constanten voor gradient fill-modi, een pointertype voor de handle-tabel, aliassen voor de gradient vertex- en primitiverecords, en drie recordtypes die Free Pascal helemaal niet declareert, rond alpha blending, transparant blitten en kleurbeheermodus
Afhankelijk is geen enkel onderdeel daarvan interessant. Alles moet kloppen voordat de parser compileert, en een compatibiliteitsunit is de natuurlijke plek omdat die als geheel tegen de headerdocumentatie vergeleken kan worden
Degene die stilletjes het verkeerde beeld tekent
Twee van die declaraties zijn niet alleen afwezig, ze zijn aanwezig en fout voor dit doel, en dit is het deel dat het onthouden waard is, zelfs als u nooit een metafile aanraakt
Free Pascal declareert het brush-creatierecord met de brush-structuur uit de runtime ingebed, en het extended-pen-record met de pen-structuur uit de runtime ingebed. Beide runtimestructuren declareren hun hatch-lid als een integer ter grootte van een pointer, omdat dat lid in een echte GDI-aanroep een handle kan bevatten. Een metafile slaat echter altijd de 32-bits vorm op, want de recordlay-out is deel van het geserialiseerde bestandsformaat en verandert niet met de bitbreedte van het proces
Op 32-bits builds komen de twee overeen en gebeurt er niets. Op Win64 is het pointergrote lid acht bytes waar het bestand er vier heeft, dus elk veld na het hatch-lid wordt vanaf de verkeerde offset gelezen. Er is geen exception, geen parseerfout en geen waarschuwing. De metafile wordt alleen verkeerd gerenderd: kleuren uit de verkeerde bytes, pendiktes uit de verkeerde bytes, en een beeld dat oogt als een renderingbug in plaats van een struct-lay-outbug. Delphi levert om precies deze reden expliciete 32-bits varianten van beide structuren, en de compatibiliteitsunit declareert ze op dezelfde manier opnieuw
// Fout op Win64: Hatch is pointergroot, het bestand slaat 32 bits op,
// en elk volgend veld verschuift vier bytes zonder foutmelding
type
TLogBrushRuntime = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: ULONG_PTR; // 8 bytes in een 64-bit-proces
end;
// Goed: de geserialiseerde lay-out, vaste breedte ongeacht de bitbreedte
type
TLogBrush32 = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: DWORD; // altijd 4 bytes, zoals opgeslagen in de metafile
end;
De algemene regel: elke structuur die zowel als runtime-API-argument als als geserialiseerde veldlay-out verschijnt, heeft twee declaraties nodig, en de geserialiseerde moet overal types met vaste breedte gebruiken. Pointergrote leden in een bestandsformaat zijn altijd een bug die op een 64-bit-build wacht
Signatuurverschillen horen in een wrapper, niet op elke aanroepplaats
De overige verschillen waren gewone signatuurverschillen, en de manier om die op te vangen is een doorsturende wrapper in plaats van een conditional op elke aanroepplaats. De functie die transformaties combineert neemt onder Free Pascal pointers waar Delphi referentieparameters neemt, dus de wrapper neemt referenties en geeft adressen door. Ze kopieert ook eerst beide bronargumenten naar locals, omdat de converter aanroepplaatsen heeft waar de doelmatrix tegelijk een van de bronnen is, en het tweemaal doorgeven van hetzelfde adres aan een functie die schrijft terwijl ze leest levert een transformatie op die subtiel verkeerd is op een manier die alleen bij geroteerde inhoud zichtbaar wordt
function CombineTransformCompat(var Dest: TXForm;
const A, B: TXForm): BOOL;
var
SrcA, SrcB: TXForm;
begin
// Eerst kopiëren: aanroepers geven Dest terecht ook als A of B door
SrcA := A;
SrcB := B;
{$IFDEF FPC}
Result := Windows.CombineTransform(@Dest, @SrcA, @SrcB);
{$ELSE}
Result := Windows.CombineTransform(Dest, SrcA, SrcB);
{$ENDIF}
end;
De rechthoek- en punttypes zijn het andere geval. Free Pascal beschouwt de metafile-rechthoek- en puntrecords als types die verschillen van de algemene graphics-varianten, dus acht toekenningsplaatsen hadden een expliciete cast tussen records met identieke lay-out nodig. Beide compilers accepteren de cast-vorm, dus die plaatsen bevatten helemaal geen conditional, wat een beetje lelijkheid waard is
Wat dit verandert voor een Free Pascal-uitrol
Vector-EMF-import werkt op Windows onder Free Pascal en levert dezelfde pagina-inhoud als de Delphi-build: paden als paden, gradients als patrooninhoud, tekst als tekst. Buiten Windows blijft de rasterweg het antwoord, en dat is een beperking van het formaat en niet van de portering. De coördinaat- en clippingstatus waar de converter in voedt wordt beschreven in het artikel over de content stream CTM- en clipping-tracker, en de vectorprimitieven die die uitzendt worden behandeld in vectorafbeeldingen, shaders en gradients
Als u uw eigen codebase controleert op dezelfde kans, is de nuttige oefening degene die dit op gang deed: som de leden op die u werkelijk gebruikt uit het framework waarvan u denkt dat u ervan afhankelijk bent. Het antwoord is vaak veel korter dan de importlijst doet vermoeden, en de echte beperking ligt meestal ergens heel anders. Op device-context gebaseerde importwegen worden in het algemeen beschreven in het artikel over printvoorbeeld en device context, en de dekking van platforms en toolchains staat op de productpagina van de losLab PDF Developer Library