Tekninen artikkeli

HotXLS Free Pascalilla: Unicode, COM-slotit ja zlib

HotXLS rakentuu Free Pascalilla ja Lazarusilla Windowsilla, ja portti riippui neljästä päätöksestä, joilla ei ole mitään tekemistä Object Pascal -syntaksin kanssa: pidä ydin DELPHIUNICODE-tilassa, määrittele OLE-structured storage -rajapinnat CORBA-rajapinnoina käsin hoidetulla referenssilaskennalla, korvaa Win32:n AES-objektitiedostot Pascal-toteutuksella ja korjaa inflate-silmukka, joka saattoi hyväksyä katkaistun ZIPin täydellisenä

Jokainen, joka on portannut kypsän Delphi-kirjaston, tuntee tämän työn muodon. Kääntäjä hyväksyy melkein kaiken ensimmäisellä kierroksella. Seuraa pitkä häntä käyttäytymiseroja, jotka kääntyvät puhtaasti ja tuottavat vääriä tuloksia, ja taulukkolaskentamoottori on niille epätavallisen altis, koska se koskettaa tekstimuotoilua, COM-structured storagea, pakkausta ja kryptografiaa yhdessä koodipolussa

Miksi ydin vaatii DELPHIUNICODEa pelkän DELPHIn sijaan?

Koska kaavamoottori riippuu siitä, että String ja Char kantavat UTF-16-semantiikkaa, ja ANSI-vaihtoehto menettää merkkejä ennen kuin mikään saavuttaa tiedoston. On houkuttelevaa rakentaa ydin FPC:n DELPHI-tilassa, koska se on yhteensopivuuskytkin, johon useimmat portit tarttuvat, ja koodi kääntyy. Sitten työkirja, jossa on kiinalaisia taulukkonimiä tai kyrillisiä nimikkeitä, kulkee laskentapolun läpi, ja merkit ovat poissa siihen mennessä, kun kirjoittaja näkee ne, ilman virhettä missään

Tila ei ole yhtenäinen kirjaston läpi, ja se on tahallista eikä sekasotkua. PNG:n tavudekooderi ja LCL-overridet tarvitsevat aidosti ANSI-signatuureja, koska ne käsittelevät tavuja ja sitä, mitä widgetset niille ojentaa. Nämä yksiköt kytkevät erillisen LX_FPC_ANSI-kytkimen. Kaksi tilaa yhdessä kirjastossa kuulostaa koodihajulta, kunnes huomaat, että vaihtoehto on tavudekooderi, joka kohtelee syötettään tekstinä

On kumppaniksi yksityiskohta, joka nappaa ihmiset myöhemmin. DELPHIUNICODE ei tee TFormatSettings.DecimalSeparatorista WideCharia FPC:n ajonaikaisessa ympäristössä. Syöte, joka kantaa Unicoden desimaalierotinta, on normalisoitava ASCII-erottimeksi Unicode-merkkijonon sisällä ensin, ja jokainen syöte, jonka erottin ei täsmää odotettuun, on hylättävä, ei hiljaa katkaistava siihen merkkiin, jota jäsennin ei tunnistanut

program ExportReport;
{$MODE DELPHI}
uses
  Interfaces,          // täytyy tulla ensin: alustaa LCL-widgetsetin
  SysUtils, lxHandle;  // ja UTF-8-muunnoskerroksen

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.

Interfaces-yksikkö ei ole valinnainen, ja sen on tultava ensin. Se on se, mikä alustaa LCL-widgetsetin ja UTF-8-muunnoskerroksen, ja HotXLS nojaa molempiin heti, kun fontit, tiedostopolut tai teksti ylittävät RTL:n ja LCL:n rajan. Konsoliohjelma, joka ohittaa sen, kääntyy ja käyttäytyy väärin millä tahansa ei-ASCII-polulla. Tämä on myös syy, miksi onnistunut käännös todistaa niin vähän täällä: portti oli todistetusti toimiva vasta, kun oikeat dokumentit oikeilla fonttinimillä ja oikeilla poluilla tekivät täyden edestakaisen matkan

Luokan VMT ei ole COM-vtable

Free Pascal ei anna sinun ojentaa luokan VMT:tä Windowsille COM-rajapinnan vtablena, vaikka määrittely näyttäisi identtiseltä sille, jonka Delphi hyväksyy. Asettelut eroavat tavoilla, jotka tuottavat kutsun väärään slottiin, mikä ilmenee kaatumisena jossain kutsupaikasta riippumattomassa. Structured storage merkitsee täällä, koska klassinen binäärinen työkirjamuoto on OLE-yhdistelmätiedosto, ja sellaisen lukeminen tai kirjoittaminen tarkoittaa ILockBytesin toteuttamista, johon Windowsin storage API kutsuu takaisin

Toimiva järjestely on CORBA-rajapinta, jossa COM-slotit on määritelty eksplisiittisesti ja AddRef ja Release hoidetaan käsin. Tämä merkitsee automaattisen referenssilaskennan luopumista näille tyypeille ja vastuun ottamista elinkaaresta, mikä on reilua vaihtokauppaa kouralliselle rajapintoja, jotka asuvat yhden yksikön sisällä. Kyseisen työn sisällä oleva tarkka ansa on QueryInterface: sen on palautettava rajapintaosoitin, ei objektiosoitin. Molemmat kääntyvät. Toinen niistä ojentaa Windowsille osoitteen, jonka ensimmäinen konesana ei ole vtable

Kaavio, joka vertaa Free Pascalin luokan VMT:tä COM-rajapinnan vtableen, jonka HotXLS:n on esitettävä Windowsin structured storage -API:lle: eri slotijärjestykset samalle Pascal-määrittelylle, sekä QueryInterface-ansa, jossa objektiosoittimen palauttaminen rajapintaosoittimen sijaan lähettää ILockBytes-kutsun luokkaslottiin ja kaataa kauas kutsupaikasta
Free Pascal kieltäytyy tarjoamasta luokan VMT:tä COM-vtablena, joten HotXLS määrittelee CORBA-rajapinnat eksplisiittisillä COM-sloteilla ja käsin hoidetuilla AddRefillä ja Releasella, ja QueryInterface palauttaa rajapintaosoittimen, jonka Windows voi dereferoida

FPC-kohtaiset määrittelyt asuvat lxOleInterfaces.incissä, lxAESBackend.incin ja lxZlibBackend.incin vieressä FPC:n lähdehakemistossa, joten kääntäjäkohtaiset valinnat istuvat yhdessä paikassa hajallaan moottorin läpi olemisen sijaan. Muoto itse ja se, miten kirjasto kulkee sen läpi, on kuvattu artikkelissa OLE2-yhdistelmätiedostojen lukeminen Pascalilla

Yksi tyyppiyksityiskohta lisää kuuluu samaan perheeseen. LargeIntin on ratkaistava Int64iksi FPC-haarassa, ja Compin kääntäjäluokittelu eroaa riittävästi kahden työkaluketjun välillä, että ylikuormien ratkaisu voi poimia eri ehdokkaan. Testaa suurten siirtymien käytöstä tiedostovirralla HGLOBAL-virran sijaan: Windowsin globaalimuistivirta kiertyy ympäri hauissa yli 4 GiB:n itsestään, joten läpäisevä testi siellä ei todista mitään omasta aritmetiikastasi

Mitä itsensä kanssa yhdenmukainen AES-toteutus kätkee

Win32:n AES-objektitiedostot, jotka Delphi-käännös linkittää, ovat OMF-muodossa, eikä Free Pascalin linkkeri voi nielaista niitä, joten FPC-haara käyttää sen sijaan Pascal-AES-toteutusta. Delphi jatkaa niiden objektitiedostojen linkittämistä, joita se aina linkitti, mikä pitää julkaistun binäärin muuttumattomana nykyisille asiakkaille

Varmistusvaatimus on se osa, joka kannattaa viedä mukanaan mihin tahansa projektiin. Datan salaaminen ja sen purkaminen uudelleen samalla toteutuksella ei todista mitään: symmetrinen algoritmi, jolla on väärä key schedule, väärä lohkojärjestys tai väärä ketjutus, on täydellisen itsensä kanssa yhdenmukainen ja tekee omasta tuloksestaan edestakaisen matkan joka kerta. Vain known-answer-vektorit nappaavat sen, tarkistaen avainlaajennuksen, lohkojärjestyksen ja CBC-ketjutuksen julkaistuja arvoja vasten. Toimita itsensä kanssa yhdenmukainen väärä toteutus, ja oire ilmestyy ensimmäisellä kerralla, kun asiakas avaa tiedoston Excelissä

Pakkauksessa oli toisen luonteen vika. Pascal-inflate-tausta voi yhä pitää tulostetta kesken, kun se on nielaissut kaiken pakatun syötteensä, joten kutsujan on jatkettava kutsumista, kunnes virta raportoi loppunsa. Loppuneen syötteen kohteleminen virran loppuna katkaisee viimeisen lohkon. Pahempaa, se muuttaa vaurioituneen arkiston hiljaisesti hyväksytyksi, mikä on täsmälleen se vikamuoto, jota vastaan artikkelin ZIP:n end-of-central-directory-tietueen validoiminen kovennus on tehty. Sääntö on, ettei etenemistä eikä valmiutta ole, on katkaisuvirhe, ei koskaan EOF

Kaksi buildijärjestelmän ansaa, jotka maksavat oikeita tunteja

LCL-hakupolkujen on edeltettävä FPC-pakettien jokerimerkkipolkuja, muuten Free Visionin Menus-yksikkö varjostaa samannimisen LCL-yksikön, ja saat PPU-tarkistussumman ristiriidan, joka ei kerro mitään kummastakaan. Lazarus-asennus, joka siirrettiin asentamisen jälkeen, voi myös jättää vanhentuneita polkuja fpc.cfgään, joten käännösten sisääntulopisteet määrittelevät yksikkö- ja binääripolut eksplisiittisesti perien ympäristön tarjoaman sijaan

Toinen ansa ei liity mitenkään Pascaliin. LF-rivinvaihdoin kirjoitettu .cmd-eräajotiedosto toimii, kunnes tiedosto kasvaa tulkin lukupuskurin kokoa suuremmaksi, jolloin call :label epäonnistuu väittämällä, ettei eräotsikkoa ole olemassa, ja vika ilmestyy siihen ohjelmaan, joka sattuu istumaan rajan yli. Jokaisen eräskriptin uudelleenkirjoittavan työkalun on kirjoitettava CRLF takaisin. Ja lazbuild --build-all tyhjentää paketin yksikkötulostushakemiston ennen kääntämistä, joten siihen hakemistoon pysäköity asetustiedosto poistetaan ennen kuin sitä ehditään lukea: pidä se ulkopuolella ja muista, että @-polku ratkaistaan pakettihakemiston suhteen, koska lazbuild kutsuu kääntäjää sieltä

Kartta kahdesta vaarakerroksesta puhtaan HotXLS Free Pascal -käännöksen takana: DELPHIUNICODE-tila, joka pitää Stringin ja Charin UTF-16:ssa, LX_FPC_ANSI-pakotie PNG:n tavudekooderille ja LCL-overrideille sekä buildiansat Free Visionin Menus-varjosta, vanhentuneista fpc.cfg-poluista, LF-only-erätiedostoista ja lazbuildin tulosten pyyhkimisestä
Ensimmäinen käännös todistaa vähän: tilakartta päättää, mitkä merkit selviävät kirjoittajalle asti, kun taas buildijärjestelmän ansat ilmestyvät tarkistussumman ristiriitoina, aaveina puuttuvina eräotsikoina ja asetustiedostoina, jotka poistetaan ennen kuin ne luetaan
// Lazarus-ruudukon vienti: TGridToXLS toimituu Lazarus-paketissa, joten
// sama DB-grid-vientikoodi toimii LCL-sovelluksessa
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;

Mitä kääntäjän varoitus on arvansa

Free Pascal raportoi alustamattomia paikallismuuttujia, joita Delphi ei raportoi, ja FPC-käännöksen ajaminen käänsi tuon eron kahdeksi oikeaksi viaksi laskentayksikössä. Toinen funktio luki laskurimuuttujan, jota ei koskaan sijoitettu ennen käyttöä, ja toinen käytti kahta koordinaattia yhdessä haarassa ennen kuin niitä laskenut koodi ajoi eri haarassa. Delphillä molemmat käyttäytyivät sen mukaan, mitä pino sattui pitämään, mikä on määritelmä bugille, joka toistuu yhdellä koneella eikä toisella

Käytännön johtopäätös on, että toinen kääntäjä kannattaa pitää silmukassa jopa tuotteelle, joka toimituu ensisijaisesti ensimmäisellä. FPC-varoitusluokkien selaaminen säännöllisesti on halpa staattisen analyysin kierros Delphi-koodikannan yli, ja se löytää defektiluokan, johon yksikään testisviitti ei luotettavasti yletä. Laajempi versiomatriisin kurinalaisuus, jonka sisällä tämä istuu, on kuvattu artikkelissa ristikkäiskääntäjäbuildimatriisi

Free Pascal- ja Lazarus-tuki Windowsille toimituu HotXLS Delphi -taulukkolaskentakomponentin mukana Lazarus-pakettina Delphi- ja C++Builder-pakettien rinnalla, rakennettuna samasta lähdekoodipuusta forkkaamisen sijaan. Tuo on harjoituksen pointti: yksi moottori, neljä työkaluketjua ja kääntäjäkohtaiset päätökset eristettyinä include-tiedostoihin, joista ne voi lukea yhdellä istumalla