PDFlibPas převádí rozšířené metasoubory na skutečný obsah stránek PDF záznam po záznamu, namísto jejich rasterizace, a právě proto zůstává importovaný graf nebo CAD výkres ostrý při libovolném přiblížení. Tento konvertor má kolem 6500 řádků a byl napsán proti VCL, takže když knihovna získala cíl Free Pascal, byl označen za nepřenositelný a nahrazen stubem. Toto označení bylo chybné a způsob, jakým bylo chybné, je užitečnou lekcí o tom, jak auditovat závislost dříve, než se rozhodnete psát kolem ní znovu
Skutečný povrch VCL těchto 6500 řádků se ukázal jako malý: třída bitmapy využívaná pro svůj pixelový formát, ukládání do streamu, handle, plátno a scanlines; třída metasouboru využívaná pro šířku, výšku a handle; a barevný typ se dvěma konstantami. Všechny tyto položky už poskytovala vlastní grafická jednotka knihovny, která existuje právě proto, aby sestavení bez VCL měla ekvivalenty. Konvertor nebyl na VCL nijak blokován. Blokovala ho jednotka Windows ve Free Pascalu
Rozdělte podle osy, na které kód skutečně závisí
Změna tedy nebyla reimplementací. Byla to jediná podmínka: z „kompilovat stub, když se sestavuje bez VCL“ na „kompilovat stub, když se nesestavuje pro Windows“. To je správná osa a vyslovení důvodu činí rozdíl zjevným. Rozšířený metasoubor je kontejner Windows. Konvertor je shora dolů parser záznamů Windows GDI. To, zda hostitelská aplikace používá VCL, jinou sadu widgetů nebo žádnou, s tím, zda lze tyto záznamy interpretovat, nesouvisí vůbec; to, zda cílem je Windows, souvisí se vším
Důsledky správné volby osy přicházejí samy. Sestavení C++Builder, která v této knihovně nedefinují symbol platformy Windows, si ponechávají stub vyhazující výjimku a chovají se přesně jako dříve. macOS stub správně drží, protože zde nejsou žádné záznamy GDI k parsování. Sestavení Delphi VCL se nedotčena. A sestavení Windows se sadou widgetů mimo VCL získává vektorový import EMF jako vedlejší efekt, který nikdo nemusel implementovat. Podmínka zarovnaná se skutečnou závislostí mění práci s platformami na změnu jednoho řádku; podmínka zarovnaná se špatnou z ní dělá přepis, na který se nikdy nedostane fronta
Mezera ve Free Pascalu byla v deklaracích, nikoli v logice
Skutečně chyběly deklarace Win32, které poskytuje jednotka Windows v Delphi a neposkytuje jednotka ve Free Pascalu. Shromáždění do jediné kompatibilní jednotky namísto rozhazování podmínek konvertorem udrželo parser čitelný. Seznam je poučný, protože ukazuje, jak nerovnoměrné je pokrytí hlaviček mezi oběma RTL: 113 konstant typů záznamů metasouboru, dva příznaky rozšířeného textového výstupu, tři konstanty režimu gradient fill, typ ukazatele tabulky handle, aliasy pro záznamy gradientních vrcholů a primitiv a tři typy záznamů, které Free Pascal nedeklaruje vůbec, pokrývající alpha blending, transparentní blitting a režim správy barev
Žádná z těch položek není sama o sobě zajímavá. Všechny musí být správně, než se parser zkompiluje, a kompatibilní jednotka je přirozené místo, protože ji lze porovnat s dokumentací hlaviček jako celek
Ten, který potichu kreslí špatný obrázek
Dvě z těchto deklarací nejen že chybí, jsou přítomné a pro tento účel chybné, a právě tato část stojí za zapamatování, i kdybyste se metasouboru nikdy nedotkli
Free Pascal deklaruje záznam pro vytváření štětce s vloženou runtime strukturou štětce a záznam rozšířeného pera s vloženou runtime strukturou pera. Obě tyto runtime struktury deklarují svůj člen hatch jako celé číslo velikosti ukazatele, protože v živém volání GDI může tento člen nést handle. Metasoubor však vždy ukládá 32bitovou podobu, protože rozložení záznamu je součástí serializovaného formátu souboru a nemění se s bitovou šířkou procesu
Na 32bitových sestaveních se obě shodnou a nic se neděje. Na Win64 má člen velikosti ukazatele osm bajtů tam, kde soubor má čtyři, takže každé pole za členem hatch se čte z nesprávného offsetu. Žádná výjimka, žádná chyba parsování, žádné varování. Metasoubor se prostě vykreslí chybně: barvy ze špatných bajtů, šířky per ze špatných bajtů a obrázek, který vypadá jako chyba vykreslování, nikoli jako chyba rozložení struktury. Delphi právě z tohoto důvodu dodává explicitní 32bitové varianty obou struktur a kompatibilní jednotka je deklaruje stejným způsobem
// Na Win64 špatně: Hatch má velikost ukazatele, soubor ukládá
// 32 bitů, a každé následující pole se posune o čtyři bajty bez chyby
type
TLogBrushRuntime = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: ULONG_PTR; // 8 bajtů v 64bitovém procesu
end;
// Správně: serializované rozložení, pevná šířka bez ohledu na bitovou šířku
type
TLogBrush32 = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: DWORD; // vždy 4 bajty, tak jak je uloženo v metasouboru
end;
Obecné pravidlo: každá struktura, která se objevuje jak jako runtime argument API, tak jako serializované rozložení polí, potřebuje dvě deklarace a ta serializovaná musí používat typy s pevnou šířkou od začátku do konce. Členy velikosti ukazatele ve formátu souboru jsou vždy chyba čekající na 64bitové sestavení
Rozdíly signatur patří do wrapperu, ne ke každému místu volání
Zbývající rozdíly byly běžné nesouhlasy signatur a způsobem, jak je pohltit, je forwarding wrapper namísto podmínky na každém místě volání. Funkce kombinující transformace bere pod Free Pascalem ukazatele tam, kde je Delphi bere referenčními parametry, takže wrapper přijímá reference a předává adresy. Navíc nejprve zkopíruje oba zdrojové argumenty do lokálních proměnných, protože konvertor má místa volání, kde cílová matice je zároveň jedním ze zdrojů, a předání téže adresy dvakrát funkci, která zapisuje, jak čte, produkuje transformaci jemně chybnou způsobem, který se projeví pouze na otočeném obsahu
function CombineTransformCompat(var Dest: TXForm;
const A, B: TXForm): BOOL;
var
SrcA, SrcB: TXForm;
begin
// Nejprve kopie: volající legitimně předávají Dest jako A nebo B
SrcA := A;
SrcB := B;
{$IFDEF FPC}
Result := Windows.CombineTransform(@Dest, @SrcA, @SrcB);
{$ELSE}
Result := Windows.CombineTransform(Dest, SrcA, SrcB);
{$ENDIF}
end;
Typy obdélníku a bodu jsou druhý případ. Free Pascal chápe záznamy obdélníku a bodu metasouboru jako odlišné typy od obecných grafických, takže osm míst přiřazení potřebovalo explicitní přetypování mezi záznamy identického rozložení. Oba kompilátory přijímají formu s přetypováním, takže tato místa nenese žádnou podmínku, což stojí za trochu ošklivosti
Co to mění pro nasazení ve Free Pascalu
Vektorový import EMF funguje na Windows pod Free Pascalem a produkuje stejný obsah stránek jako sestavení Delphi: cesty jako cesty, přechody jako obsah vzorků, text jako text. Mimo Windows zůstává odpovědí rastrová cesta a to je limit formátu, nikoli portu. Souřadnicový a ořezový stav, do něhož konvertor zapisuje, popisuje článek o sledování CTM a ořezů ve content streamu a vektorové primitivy, které vydává, pokrývá vektorová grafika, shadery a přechody
Pokud auditujete vlastní kódovou základnu za stejnou příležitostí, užitečné cvičení je to, které to všechno odstartovalo: vyjmenujte členy, které ze frameworku, o němž si myslíte, že na něm závisíte, skutečně používáte. Odpověď bývá mnohem kratší, než naznačuje seznam importů, a skutečné omezení je obvykle úplně jinde. Importní cesty založené na device contextu obecně popisuje článek o náhledu tisku a device contextu a pokrytí platforem a toolchainů uvádí stránka produktu losLab PDF Developer Library