Tehnički članak

HotXLS na Free Pascalu: Unicode, COM slotovi i zlib

HotXLS se gradi pod Free Pascalom i Lazarusom na Windowsima, i port se svodio na četiri odluke koje nemaju ništa s Object Pascal sintaksom: držati core u DELPHIUNICODE modu, deklarirati OLE structured-storage interface kao CORBA interface s ručno vođenim reference countingom, zamijeniti Win32 AES objektne datoteke Pascal implementacijom, i popraviti inflate petlju koja je mogla prihvatiti skraćeni ZIP kao kompletan

Tko je ikad portirao zrelu Delphi biblioteku zna oblik ovog posla. Kompajler prihvati skoro sve u prvom prolazu. Slijedi dug rep behavioralnih razlika koje se čisto kompajliraju a proizvode krive rezultate, i spreadsheet engine je njima neobično izložen jer dodiruje text encoding, COM structured storage, kompresiju i kriptografiju u jednom code putu

Zašto core inzistira na DELPHIUNICODE a ne na običnom DELPHI?

Jer formula engine ovisi o tome da String i Char nose UTF-16 semantiku, a ANSI alternativa gubi znakove prije nego išta stigne do datoteke. Primamljivo je graditi core u FPC DELPHI modu, jer je to kompatibilnost switch za koji se većina portova poseže, i kod se kompajlira. Onda radna knjiga s kineskim imenima listova ili ćiriličnim labelama prođe kroz calculation put natrag i znakovi su nestali do trenutka kad ih writer vidi, bez ijedne greške bilo gdje

Mod nije uniforman kroz cijelu biblioteku, i to je namjerno a ne neuredno. PNG byte dekoder i LCL overridei stvarno trebaju ANSI signature, jer rade s bajtovima i s onim što im widgetset pruža. Ti uniti uključuju poseban LX_FPC_ANSI switch. Dva moda u jednoj biblioteci zvuči kao smell dok ne primijetite da je alternativa byte dekoder koji svoj ulaz tretira kao tekst

Tu je prateći detalj koji kasnije ulovi ljude. DELPHIUNICODE ne čini TFormatSettings.DecimalSeparator WideCharom u FPC runtimeu. Ulaz koji nosi Unicode decimalni separator mora se najprije normalizirati na ASCII separator unutar Unicode stringa, i svaki ulaz čiji se separator ne poklapa s očekivanim mora se odbiti umjesto tiho skratiti na znaku koji parser nije prepoznao

program ExportReport;
{$MODE DELPHI}
uses
  Interfaces,          // mora doći prvi: inicijalizira LCL widgetset
  SysUtils, lxHandle;  // i UTF-8 konverzijski sloj

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 unit nije opcionalan i mora doći prvi. On je ono što inicijalizira LCL widgetset i UTF-8 konverzijski sloj, i HotXLS se oslanja na oba čim fontovi, putanje datoteka ili tekst prijeđu RTL i LCL granicu. Console program koji ga preskoči kompajlirat će se i pogrešat će na svakoj ne-ASCII putanji. To je ujedno i razlog zašto uspješna kompilacija ovdje dokazuje tako malo: port je dokazano radio tek kad su pravi dokumenti s pravim imenima fontova i pravim putanjama napravili potpuni round trip

Class VMT nije COM vtable

Free Pascal vam neće dopustiti da class VMT predate Windowsima kao COM interface vtable, čak i kad deklaracija izgleda identično onoj koju Delphi prihvaća. Rasporedi se razlikuju na načine koji proizvode poziv u krivi slot, što se manifestira kao crash negdje nepovezan s call siteom. Structured storage je tu bitan jer je klasični binary format radne knjige OLE compound datoteka, i čitati je ili pisati znači implementirati ILockBytes u koje će Windows storage API pozivati natrag

Radni aranžman je CORBA interface s eksplicitno deklariranim COM slotovima i AddRef i Release vođenim rukom. To znači odustati od automatskog reference countinga za ove tipove i preuzeti odgovornost za životni vijek, što je pošten trade za šaku interfacea koji žive unutar jednog unita. Specifična zamka u tom poslu je QueryInterface: mora vratiti interface pokazivač, a ne pokazivač objekta. Oboje se kompajlira. Jedno od toga predaje Windowsima adresu čija prva mašinska riječ nije vtable

Dijagram koji uspoređuje Free Pascal class VMT s COM interface vtableom koji HotXLS mora prezentirati Windows structured storage APIju: različiti redoslijedi slotova za istu Pascal deklaraciju, plus QueryInterface zamka gdje vraćanje pokazivača objekta umjesto interface pokazivača šalje ILockBytes poziv u class slot i pukne daleko od call sitea
Free Pascal odbija poslužiti class VMT kao COM vtable, pa HotXLS deklarira CORBA interface s eksplicitnim COM slotovima i ručno vođenim AddRef i Release, a QueryInterface vraća interface pokazivač koji Windowsi mogu dereferencirati

FPC-specifične deklaracije žive u lxOleInterfaces.inc, pokraj lxAESBackend.inc i lxZlibBackend.inc u FPC source direktoriju, pa kompajlerski specifični izbori sjede na jednom mjestu umjesto da su rasuti kroz engine. Sam format i kako ga biblioteka prelazi opisani su u čitanju OLE2 compound datoteka u Pascalu

Još jedan tip detalj pripada istoj familiji. LargeInt se mora razriješiti u Int64 u FPC grani, i kompajlerska klasifikacija Compa se dovoljno razlikuje između dva toolchaina da overload resolution može odabrati drugog kandidata. Testirajte large-offset ponašanje file streamom umjesto HGLOBAL streamom: Windows global-memory stream sam se omota oko seekova preko 4 GiB, pa prolazni test tu ne dokazuje ništa o vlastitoj aritmetici

Što samokonzistentna AES implementacija krije

Win32 AES objektne datoteke koje Delphi build linka su OMF, i Free Pascal linker ih ne može konzumirati, pa FPC grana umjesto toga koristi Pascal AES implementaciju. Delphi i dalje linka objektne datoteke koje je oduvijek linkao, što izdani binary drži nepromijenjenim za postojeće kupce

Zahtjev verifikacije je dio koji vrijedi ponijeti u svaki projekt. Šifrirati podatke i dešifrirati ih opet istom implementacijom ne dokazuje apsolutno ništa: simetrični algoritam s krivim key scheduleom, krivim redoslijedom blokova ili krivim chainingom je savršeno samokonzistentan i svaki će put round-tripati svoj vlastiti izlaz. Samo known-answer vektori ga ulove, provjeravajući key expansion, redoslijed blokova i CBC chaining protiv objavljenih vrijednosti. Isporučite samokonzistentnu krivu implementaciju i simptom se pojavi prvi put kad kupac otvori datoteku u Excelu

Kompresija je imala defekt drugačijeg karaktera. Pascal inflate backend može još imati izlaz na čekanju nakon što je potrošio sav kompresirani ulaz, pa pozivatelj mora zvati dok stream ne javi svoj kraj. Tretirati iscrpljeni ulaz kao kraj streama skraćuje zadnji blok. Gore, pretvara oštećenu arhivu u tiho prihvaćenu, a to je točno failure mod koji hardening u validaciji ZIP end-of-central-directory recorda postoji da spriječi. Pravilo je da nema napretka plus nije gotovo je truncation greška, nikad EOF

Dvije build-sustav zamke koje koštaju stvarnih sati

LCL search putanje moraju prethoditi FPC package wildcard putanjama, ili Free Vision Menus unit zasjeni LCL unit istog imena i dobijete PPU checksum mismatch koji ne govori ništa o nijednome. Lazarus instalacija premještena nakon instalacije može također ostaviti zastarjele putanje u fpc.cfg, pa build entry pointi specificiraju unit i binary putanje eksplicitno umjesto da nasljeđuju što god okolina ponudi

Druga zamka nema nikakve veze s Pascalom. .cmd batch datoteka pisana s LF završetcima linija radi dok datoteka ne naraste preko veličine interpreterovog read buffera, u kojem trenutku call :label puca s tvrdnjom da batch labela ne postoji, i neuspjeh se pojavi na bilo kojem programu koji slučajno sjedi iza granice. Svaki alat koji prepisuje batch skriptu mora napisati CRLF natrag. I lazbuild --build-all očisti package unit output direktorij prije kompilacije, pa options datoteka parkirana u tom direktoriju bude izbrisana prije nego što se može pročitati: držite je izvan, i zapamtite da se @ putanja razrješuje relativno prema package direktoriju jer lazbuild poziva kompajler odatle

Karta dvaju slojeva opasnosti iza čiste HotXLS Free Pascal kompilacije: DELPHIUNICODE mod koji drži String i Char u UTF-16, LX_FPC_ANSI bijeg za PNG byte dekoder i LCL overridee, i build zamke od Free Vision Menus sjenčanja, zastarjelih fpc.cfg putanja, LF-only batch datoteka i lazbuild brisanja outputa
Prva kompilacija dokazuje malo: karta modova odlučuje koji znakovi prežive do writera, dok build-sustav zamke isplivaju kao checksum mismatchevi, fantomski nedostajuće labele i options datoteke izbrisane prije nego što se pročitaju
// Lazarus grid export: TGridToXLS dolazi u Lazarus paketu, pa isti
// DB-grid export kod radi u LCL aplikaciji
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;

Vrijednost kompajlerskih upozorenja

Free Pascal javlja neinicijalizirane lokalne varijable koje Delphi ne javlja, i pokretanje FPC builda pretvorilo je tu razliku u dva stvarna defekta u calculation unitu. Jedna function čitala je count varijablu koja nikad nije bila dodijeljena prije upotrebe, a druga je koristila dvije koordinate u jednoj grani prije nego što je kod koji ih je računao trčao u drugoj grani. Pod Delphijem su se obje ponašale prema tome što je stog slučajno držao, a to je definicija buga koji se reproducira na jednoj mašini a ne na drugoj

Praktični zaključak je da vrijedi drugi kompajler držati u petlji čak i za proizvod koji se prvenstveno isporučuje na prvome. Pregledavati FPC warning klase periodično je jeftin static analysis prolaz preko Delphi codebasea, i nalazi kategoriju defekta do koje nijedan test suite pouzdano ne dopire. Šira disciplina verzija-matrice u koju se ovo uklapa opisana je u cross-compiler build matrici

Free Pascal i Lazarus podrška za Windows isporučuje se uz HotXLS Delphi spreadsheet komponentu kao Lazarus paket uz Delphi i C++Builder pakete, građena iz istog stabla izvora umjesto forka. U tome je poanta vježbe: jedan engine, četiri toolchaina, i kompajlerski specifične odluke izolirane u include datotekama gdje ih se može pročitati u jednom sjedenju