Tehnični članak

HotXLS na Free Pascal: Unicode, COM mesta in zlib

HotXLS se pod Free Pascal in Lazarus na Windows zgradi, preselitev pa je vzklila na štirih odločitvah, ki nimajo nič s skladnjo Object Pascal: jedro obdržati v načinu DELPHIUNICODE, vmesnike strukturirane shrambe OLE razglasiti kot vmesnike CORBA z ročno vodenim štetjem sklicev, objektne datoteke AES Win32 zamenjati s Pascal implementacijo in popraviti zanko inflate, ki bi lahko sprejela odrezan ZIP kot cel

Vsak, ki je preselil zrelo Delphi knjižnico, pozna obliko tega dela. Prevajalnik ob prvem prehodu sprejme skoraj vse. Sledi dolg rep vedenjskih razlik, ki se čisto prevedejo in proizvedejo napačne rezultate, preglednični motor pa je nanje nenavadno izpostavljen, ker se v eni sami kodni poti dotakne kodiranja besedila, strukturirane shrambe COM, stiskanja in kriptografije

Zakaj jedro vztraja pri DELPHIUNICODE in ne pri navadnem DELPHI?

Ker je motor formul odvisen od tega, da String in Char nosita semantiko UTF-16, alternativi ANSI pa znaki izgubijo, preden karkoli pride do datoteke. Mamljivo je zgraditi jedro v FPC načinu DELPHI, saj je to stikalo združljivosti, po katerem sega večina preselitev, in koda se prevede. Potem delovna knjiga s kitajskimi imeni listov ali ciriličnimi oznakami naredi povratni skozi računsko pot in znaki so izginili, preden jih pisec sploh vidi — brez nobene napake

Način ni povsod enoten čez knjižnico in to je namerno, ne neurejeno. Dekodirnik bajtov PNG in prevali LCL resnično potrebujejo podpise ANSI, ker upravljata z bajti in s tem, kar jima podala gradnikova množica (widgetset). Te enote vklopijo ločeno stikalo LX_FPC_ANSI. Dva načina v eni knjižnici zveni kot zadah, dokler ne opazite, da je alternativa dekodirnik bajtov, ki svoj vhod obravnava kot besedilo

Obstaja še podrobnost v spremstvu, ki ljudi ujame kasneje. DELPHIUNICODE ne naredi TFormatSettings.DecimalSeparator v WideChar v izvajalnem okolju FPC. Vhod, ki nosi decimalno ločilo Unicode, se mora najprej normalizirati na ločilo ASCII znotraj niza Unicode, vsak vhod, katerega ločilo se ne ujema s pričakovanim, pa mora biti zavrnjen in ne tiho odrezan pri znaku, ki ga razčlenjevalnik ni prepoznal

program ExportReport;
{$MODE DELPHI}
uses
  Interfaces,          // mora priti prvi: inicializira LCL widgetset
  SysUtils, lxHandle;  // in plast pretvorbe UTF-8

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.

Enota Interfaces ni neobvezna in mora priti prva. Ona inicializira gradnikovo množico LCL in plast pretvorbe UTF-8, HotXLS pa se zanaša na oboje, takoj ko pisave, poti datotek ali besedilo prečkata mejo med RTL in LCL. Konzolni program, ki jo preskoči, se bo prevedel in se bo slabo vedel na kateri koli ne-ASCII poti. To je tudi razlog, zakaj uspešen prevod tu dokazuje tako malo: preselitev je bila pokazno delujoča šele, ko so resnični dokumenti z resničnimi imeni pisav in resničnimi potmi naredili poln povratni skok

Razredni VMT ni COM vtable

Free Pascal vam ne bo dovolil podati razrednega VMT Windowsom kot vtable vmesnika COM, tudi če deklaracija izgleda identična tisti, ki jo Delphi sprejme. Razporeditvi se razlikujeta na načine, ki proizvedejo klic v napačno mesto, kar se manifestira kot sesutje nekam, nepovezanega z mestom klica. Strukturirana shramba je tu pomembna, ker je klasični binarni format delovne knjige sestavljena datoteka OLE, branje ali pisanje take pa pomeni implementirati ILockBytes, v katere bo shrambni API Windows klical nazaj

Delujoča ureditev je vmesnik CORBA z izrecno razglašenimi COM mesti in ročno vodenima AddRef in Release. To pomeni odreči se samodejnemu štetju sklicev za te tipe in prevzeti odgovornost za življenjsko dobo — poštena menjava za peščico vmesnikov, ki živijo znotraj ene enote. Določena past znotraj tega dela je QueryInterface: mora vrniti kazalec na vmesnik, ne kazalca na objekt. Oboje se prevede. En od njiju poda Windowsom naslov, katerega prva strojna beseda ni vtable

Diagram, ki primerja razredni VMT Free Pascal z vtable vmesnika COM, ki ga mora HotXLS ponuditi shrambnemu API Windows: različna vrstna mesta za isto Pascal deklaracijo, plus past QueryInterface, kjer vračanje kazalca objekta namesto kazalca vmesnika pošlje klic ILockBytes v razredno mesto in sesuje daleč od mesta klica
Free Pascal odkloni postrežbo razrednega VMT kot COM vtable, zato HotXLS razglasi vmesnike CORBA z izrecnimi COM mesti in ročno vodenima AddRef in Release, QueryInterface pa vrne kazalec na vmesnik, ki ga Windows more razrešiti

Deklaracije, specifične za FPC, živijo v lxOleInterfaces.inc, poleg lxAESBackend.inc in lxZlibBackend.inc v izvornem imeniku FPC, tako da izbire, specifične za prevajalnik, sedejo na enem mestu in niso raztresene čez motor. Sam format in kako se vanj krmarita knjižnici sta opisana v brananju sestavljenih datotek OLE2 v Pascalu

Še ena tipna podrobnost sodi v isto družino. LargeInt se mora v veji FPC razrešiti v Int64, razvrstitev tipa Comp s strani prevajalnika pa se med toolchainoma dovolj razlikuje, da lahko razreševanje preobremenitev izbere drugega kandidata. Vedenje velikih odmikov testirajte s tokom datoteke in ne s tokom HGLOBAL: tok globalnega pomnilnika Windows se sam ovije pri iskanjih čez 4 GiB, tako da uspešen test tam ne dokaže nič o vaši lastni aritmetiki

Kaj skriva samoskladna implementacija AES

Objektne datoteke AES Win32, ki jih povezuje gradnja Delphi, so OMF in jih povezovalnik Free Pascal ne more pogoltniti, zato veja FPC namesto njih uporablja Pascal implementacijo AES. Delphi še naprej povezuje objektne datoteke, ki jih je vedno, kar izdano binarko obdrži nespremenjeno za obstoječe stranke

Zahteva po preverjanju je del, ki ga vredi odnesti v vsak projekt. Šifriranje podatkov in ponovno dešifriranje z isto implementacijo ne dokaže nič: simetrični algoritem z napačnim razporedom ključev, napačnim vrstnim redom blokov ali napačnim veriženjem je popolnoma samoskladen in bo vsakič naredil povratni skok svojega izhoda. Ujamejo ga le vektorji znanih odgovorov, ki preverijo širitev ključev, vrstni red blokov in veriženje CBC proti objavljenim vrednostim. Pošljite samoskladno napačno implementacijo in simptom se pokaže prvič, ko stranka odpre datoteko v Excelu

Stiskanje je imelo pomanjkljivost drugega značaja. Zaledje inflate v Pascalu lahko še ima izhod v čakanju, potem ko je porabil ves stisnjeni vhod, zato mora klicatelj klicati naprej, dokler tok ne sporoči svojega konca. Obravnava izčrpanega vhoda kot konec toka odreže zadnji blok. Slabše — poškodovan arhiv spremeni v tiho sprejetega, kar je natanko način odpovedi, ki mu je utrjevanje v validaciji zapisa ZIP end-of-central-directory namenjeno preprečiti. Pravilo je: brez napredka plus ni končano je napaka odrezanja in nikoli EOF

Dve pasti gradbenega sistema, ki staneta prave ure

Iskalne poti LCL morajo iti pred nadomestnimi potmi paketov FPC, sicer enota Menus Free Vision zasenči enoto LCL z istim imenom in dobite neskladje kontrolne vsote PPU, ki o čemer koli od obeh ne pove nič. Namestitev Lazarus, premaknjena po namestitvi, lahko tudi pusti zastarele poti v fpc.cfg, zato vstopne točke gradnje izrecno navedejo poti enot in binarnih datotek namesto da bi podedovale, kar okolje ponudi

Druga past nima nič s Pascalom. Paketna datoteka .cmd, zapisana s konci vrstic LF, deluje, dokler datoteka ne zraste čez velikost bralnega medpomnilnika tolmača — takrat call :label spodleti s trditvijo, da paketna oznaka ne obstaja, spodletel pa se pokaže pri katerem koli programu, ki slučajno sede čez mejo. Vsako orodje, ki prepiše paketni skript, mora zapisati CRLF nazaj. In lazbuild --build-all počisti izhodni imenik enot paketa, preden prevaja, tako da je datoteka možnosti, parkirana v tem imeniku, izbrisana, preden jo je mogoče prebrati: hranite jo zunaj in pomnite, da se pot @ razreši relativno na imenik paketa, ker lazbuild prevajalnik prikliče od tam

Zemljevid dveh plasti nevarnosti za čistim prevodom HotXLS pod Free Pascal: način DELPHIUNICODE, ki obdrži String in Char v UTF-16, zasilni izhod LX_FPC_ANSI za dekodirnik bajtov PNG in prevali LCL ter gradbene pasti iz zasenčitve Menus Free Vision, zastarelih poti fpc.cfg, paketnih datotek samo-LF in brisanja izhoda lazbuild
Prvi prevod dokazuje malo: zemljevid načinov odloči, kateri znaki preživijo do pisca, gradbene pasti pa pridejo na dan kot neskladja kontrolnih vsot, duhovske manjkajoče oznake in datoteke možnosti, izbrisane, preden so bile prebrane
// Izvoz mreže Lazarus: TGridToXLS pride z paketom Lazarus, zato ista
// koda izvoza DB-grid deluje v aplikaciji LCL
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;

Kaj je vredno opozorilo prevajalnika

Free Pascal sporoča neinicializirane krajevne spremenljivke, ki jih Delphi ne, in zagon gradnje FPC je to razliko spremenil v dve resnični pomanjkljivosti v računski enoti. Ena funkcija je brala številsko spremenljivko, ki nikoli ni bila dodeljena pred uporabo, druga pa je v eni veji uporabila dve koordinati, preden je koda, ki ju je izračunala, tekla v drugi veji. Pod Delphi sta se obe vedli po tistem, kar je sklad slučajno držal — to je definicija hrošča, ki se reproducira na enem računalniku in ne na drugem

Praktični sklep je, da je drugi prevajalnik vreden ostati v zanki, tudi za izdelek, ki se primarno pošilja na prvem. Občasno preleteti razrede opozoril FPC je poceni prehod statične analize čez Delphi kodno bazo in najde kategorijo pomanjkljivosti, do katere nobena testna zbirka zanesljivo ne pride. Širša disciplina matrike verzij, v kateri to sede, je opisana v matriki gradenj čez prevajalnike

Podpora Free Pascal in Lazarus za Windows pride z HotXLS Delphi spreadsheet component kot paket Lazarus ob paketih Delphi in C++Builder, zgrajen iz istega izvornega drevesa in ne iz vilice. To je poanta vaje: en motor, štirje toolchaini in odločitve, specifične za prevajalnik, izolirane v vključitvenih datotekah, kjer jih je mogoče prebrati v enem sedenju