Műszaki cikk

HotXLS Free Pascal alatt: Unicode, COM slotok és a zlib

A HotXLS Free Pascal és Lazarus alatt fordul Windowson, és a port négy olyan döntésen múlt, amelyeknek semmi közük az Object Pascal szintaxisához: a mag DELPHIUNICODE módban tartása, az OLE strukturált tárolási interfészek CORBA interfészként deklarálása kézzel kezelt hivatkozásszámlálással, a Win32 AES objektumfájlok Pascal implementációval való helyettesítése, és egy olyan inflate ciklus megjavítása, amely csonkolt ZIP-et fogadhatott el teljesként

Aki már portolt érett Delphi könyvtárat, ismeri ennek a munkának az alakját. A fordító az első menetben majdnem mindent elfogad. Aztán hosszú farok következik: viselkedési különbségek, amelyek tisztán fordulnak és rossz eredményt adnak, és egy táblázatmotor szokatlanul kitéve van nekik, mert egyetlen kódúton érinti a szövegkódolást, a COM strukturált tárolást, a tömörítést és a kriptográfiát

Miért ragaszkodik a mag a DELPHIUNICODE-hoz, nem a sima DELPHI-hez?

Mert a képletmotor arra épít, hogy a String és a Char UTF-16 szemantikát hordoz, az ANSI alternatíva pedig elveszíti a karaktereket, mielőtt bármi elérné a fájlt. Csábító a magot FPC DELPHI módban építeni, mert ez az a kompatibilitási kapcsoló, amelyet a legtöbb port megfog, és a kód lefordul. Aztán egy kínai lapneveket vagy cirill címkéket viselő munkafüzet körbeutazik a számítási úton, és a karakterek addigra odavesznek, mire az író meglátja őket, hiba nélkül bárhol

A mód nem egységes a könyvtárban, és ez szándékos, nem rendetlenség. A PNG bájtdékódernek és az LCL felülírásoknak valóban ANSI aláírásokra van szüksége, mert bájtokkal és azzal kereskednek, amit a widgetset átad nekik. Ezek a unitok külön LX_FPC_ANSI kapcsolót kapcsolnak be. Két mód egy könyvtárban kódszagnak hangzik, amíg észre nem veszi, hogy az alternatíva egy olyan bájtdékóder, amely szövegként kezeli a bemenetét

Van egy kísérő részlet, amely később kap el embereket. A DELPHIUNICODE nem teszi a TFormatSettings.DecimalSeparator-t WideChar-rá az FPC futtatókörnyezetben. A Unicode tizedeselválasztót viselő bemenetet előbb ASCII elválasztóra kell normalizálni a Unicode sztringen belül, és minden olyan bemenetet el kell utasítani, amelynek az elválasztója nem egyezik a várttal, nem pedig csendben csonkolni az elemző által fel nem ismert karakternél

program ExportReport;
{$MODE DELPHI}
uses
  Interfaces,          // elöl kell állnia: inicializálja az LCL widgetsetet
  SysUtils, lxHandle;  // és az UTF-8 konverziós réteget

var
  Book: TXLSWorkbook;
begin
  Book := TXLSWorkbook.Create(nil);
  try
    Book.LoadFromFile('input.xls');
    Book.Sheets[0].AsString[1, 1] := 'Quarterly summary';
    Book.SaveToFile('output.xls');
  finally
    Book.Free;
  end;
end.

A Interfaces unit nem opcionális, és elöl kell állnia. Ez inicializálja az LCL widgetsetet és az UTF-8 konverziós réteget, a HotXLS pedig mindkettőtől függ, amint fontok, fájlútvonalak vagy szöveg átlépik az RTL és az LCL határát. Az ezt kihagyó konzolprogram lefordul, és minden nem ASCII útvonalon hibázni fog. Ez az oka annak is, hogy egy sikeres fordítás itt keveset bizonyít: a port csak akkor volt bizonyíthatóan működőképes, ha valódi dokumentumok valódi fontnevekkel és valódi útvonalakkal teljes kört tettek meg

Az osztály VMT nem COM vtable

A Free Pascal nem engedi, hogy osztály VMT-t adjon át Windowson COM interfész vtable-ként, akkor sem, ha a deklaráció azonosnak látszik azzal, amelyet a Delphi elfogad. Az elrendezések úgy térnek el, hogy rossz slotba intéznek hívást, ami a hívási helytől független helyen megnyilvánuló crashként jelentkezik. A strukturált tárolás itt azért fontos, mert a klasszikus bináris munkafüzetformátum OLE összetett fájl, és egy ilyen olvasása vagy írása azt jelenti, hogy olyan ILockBytes-t kell implementálni, amelybe a Windows tárolási API visszahív

A működő megoldás CORBA interfész explicit módon deklarált COM slotokkal, kézzel kezelt AddRef-fel és Release-el. Ez azt jelenti, hogy feladják e típusok automatikus hivatkozásszámlálását, és átvállalják az élettartam felelősségét, ami fair üzlet néhány interfészért, amelyek egyetlen unitban élnek. Az e munkán belüli konkrét csapda a QueryInterface: interfészpointert kell adjon vissza, nem az objektumpointert. Mindkettő lefordul. Az egyik olyan címet ad a Windowsnak, amelynek első gépszava nem vtable

Ábra a Free Pascal osztály VMT-jének és annak a COM interfész vtable-nak az összehasonlítására, amelyet a HotXLS-nek a Windows strukturált tárolási API felé kell mutatnia: eltérő slotsorrend ugyanarra a Pascal deklarációra, plusz a QueryInterface csapda, amelyben az objektumpointer visszatérítése interfészpointer helyett egy ILockBytes hívást osztályslotba küld, és messze a hívási helytől crashel
A Free Pascal nem hajlandó osztály VMT-t COM vtable-ként szolgáltatni, ezért a HotXLS explicit COM slotokkal, kézzel kezelt AddRef-fel és Release-szel deklarál CORBA interfészeket, és a QueryInterface azt az interfészpointert adja vissza, amelyet a Windows fel tud oldani

Az FPC-specifikus deklarációk a lxOleInterfaces.inc-ben élnek, az lxAESBackend.inc és a lxZlibBackend.inc mellett az FPC forráskönyvtárban, tehát a fordítóspecifikus döntések egy helyen ülnek, nem szóródnak szét a motorban. Magát a formátumot és azt, hogyan navigálja a könyvtár, a OLE2 összetett fájlok olvasása Pascalban írja le

Még egy típusrészlet tartozik ugyanebbe a családba. A LargeInt-nek az FPC ágon Int64-re kell feloldódnia, és a Comp fordítói besorolása elég mértékben eltér a két toolchain között ahhoz, hogy a túlterhelésfeloldás másik jelöltet választhasson. Tesztelje a nagy-offset viselkedést fájlstreammel, nem HGLOBAL streammel: a Windows globálmemória-stream 4 GiB feletti pozicionálásnál önmagától körbeér, tehát egy ott átmenő teszt semmit sem bizonyít a saját számtanáról

Mit rejt egy önkonzisztens AES implementáció?

A Win32 AES objektumfájlok, amelyeket a Delphi build linkel, OMF formátumúak, és a Free Pascal linker nem tudja őket fogyasztani, ezért az FPC ág helyette Pascal AES implementációt használ. A Delphi továbbra is linkeli az objektumfájlokat, amelyeket mindig is, ez a kiadott bináris változatlanul tartásával szolgál a meglévő ügyfeleknek

A verifikációs követelmény az a rész, amelyet érdemes bármely projektbe vinni. Az adat titkosítása és ugyanazzal az implementációval való visszafejtése semmit sem bizonyít: egy szimmetrikus algoritmus rossz kulcsütemezéssel, rossz blokksorrenddel vagy rossz láncolással tökéletesen önkonzisztens, és minden alkalommal körbeviszi a saját kimenetét. Csak a known-answer vektorok kapják el, a kulcskiterjesztést, a blokksorrendet és a CBC láncolást ellenőrizve a publikált értékekkel szemben. Szállítson egy önkonzisztens, rossz implementációt, és a tünet azon az első alkalommal jelentkezik, amikor egy ügyfél megnyitja a fájlt Excelben

A tömörítés más jellegű hibát hordozott. Egy Pascal inflate backendnek azután is lehet kimenete függőben, hogy elfogyasztotta az összes tömörített bemenetét, tehát a hívónak addig kell hívnia, amíg a stream a végét jelenti. A kimerült bemenet stream-végként kezelése az utolsó blokkot csonkolja. Rosszabb, hogy a sérült archívumot csendben elfogadottá teszi, ami pontosan az a hibamód, amelynek megakadályozására a ZIP end-of-central-directory rekord validálása megszilárdítása létezik. A szabály az, hogy a nincs haladás plusz nem kész kombináció csonkolási hiba, sosem EOF

Két buildrendszeri csapda, amely igazi órákba kerül

Az LCL keresési útvonalaknak meg kell előzniük az FPC csomag wildcard útvonalakat, különben a Free Vision Menus unitja árnyékolja az azonos nevű LCL unitot, és olyan PPU ellenőrzőösszeg-eltérést kap, amely egyikről sem mond semmit. Egy telepítés után áthelyezett Lazarus telepítés elavult útvonalakat hagyhat a fpc.cfg-ben, tehát a build belépési pontok explicit módon adják meg a unit- és binárisútvonalakat, a környezet által kínáltak öröklése helyett

A második csapdának semmi köze a Pascalhoz. LF soremelésű .cmd kötegfájl addig működik, amíg a fájl meg nem haladja az értelmező olvasási bufferének méretét, amikor a call :label azzal bukik meg, hogy a kötegcímke nem létezik, és a hiba annál a programnál jelentkezik, amely éppen a határon túl ül. Bármely eszköznek, amely kötegparancsfájlt ír át, CRLF-et kell visszaírnia. És a lazbuild --build-all fordítás előtt törli a csomag unit kimeneti könyvtárát, tehát az abban a könyvtárban parkolt beállításfájl törlődik, mielőtt beolvasták volna: tartsa kívül, és ne feledje, hogy az @ útvonal a csomagkönyvtárhoz képest oldódik fel, mert a lazbuild onnan hívja a fordítót

Térkép a két veszélyrétegről egy tiszta HotXLS Free Pascal fordítás mögött: a DELPHIUNICODE mód, amely a Stringet és a Char-t UTF-16-ban tartja, az LX_FPC_ANSI kijárat a PNG bájtdékóder és az LCL felülírások számára, valamint build csapdák a Free Vision Menus árnyékolásból, az elavult fpc.cfg útvonalakból, a csak LF kötegfájlokból és a lazbuild kimenettörléséből
Az első fordítás keveset bizonyít: a módtérkép dönti el, mely karakterek élik meg az írót, míg a buildrendszeri csapdák ellenőrzőösszeg-eltérésként, kísérteties hiányzó címkékként és olvasás előtt törölt beállításfájlokként bukkannak fel
// Lazarus grid export: a TGridToXLS a Lazarus csomagban szállul, tehát
// ugyanaz a DB-grid exportkód LCL alkalmazásban is működik
var
  Exporter: TGridToXLS;
begin
  Exporter := TGridToXLS.Create(nil);
  try
    Exporter.DBGrid := GridOrders;
    Exporter.WorksheetName := 'Orders';
    Exporter.ExportHeader := True;
    Exporter.SetColumnsWidth := True;
    Exporter.ExportDBGrid;
    Exporter.SaveAs('orders.xls');
  finally
    Exporter.Free;
  end;
end;

Mennyit ér egy fordítói figyelmeztetés

A Free Pascal olyan inicializálatlan lokális változókat jelent, amelyeket a Delphi nem, és az FPC build futtatása ezt a különbséget két valós hibává fordította a számítási unitban. Az egyik függvény egy használat előtt soha hozzá nem rendelt darabszám-változót olvasott, a másik két koordinátát használt az egyik ágban, mielőtt az őket kiszámoló kód egy másik ágban lefutott volna. Delphi alatt mindkettő úgy viselkedett, ahogy a stack éppen tartalmazott, ami annak definíciója, hogy egy hiba az egyik gépen reprodukálódik, a másikon nem

A gyakorlati következtetés az, hogy a második fordítót érdemes a hurokban tartani egy olyan termék esetében is, amely elsősorban az elsőn szállul. Az FPC figyelmeztető osztályok rendszeres átfuttatása olcsó statikaelemzési menet egy Delphi kódbázison, és olyan hibakategóriát talál, amelyet egyetlen tesztcsomag sem ér el megbízhatóan. Az ezt körülölelő tágabb verziómátrix-fegyelmet a keresztfordítói buildmátrix írja le

A Free Pascal és Lazarus Windowstámogatása a HotXLS Delphi táblázatkezelő komponenssel szállul Lazarus csomagként a Delphi és C++Builder csomagok mellett, ugyanabból a forrásfából építve, fork helyett. Ebben van a gyakorlat lényege: egy motor, négy toolchain, és a fordítóspecifikus döntések izolálva include fájlokban, ahol egy ülésben áttekinthetők