HotXLS bouwt onder Free Pascal en Lazarus op Windows, en de port draaide om vier beslissingen die niets met Object Pascal-syntaxis te maken hebben: de core houden in DELPHIUNICODE-modus, de OLE structured-storage-interfaces declareren als CORBA-interfaces met handmatig beheerde reference counting, de Win32 AES-objectbestanden vervangen door een Pascal-implementatie, en een inflate-loop fixen die een afgekapte ZIP als compleet kon accepteren
Iedereen die ooit een volwassen Delphi-library heeft geporteerd, kent de vorm van dit werk. De compiler accepteert bij de eerste pas vrijwel alles. Daarna volgt een lange staart van gedragsverschillen die netjes compileren en toch verkeerde resultaten opleveren, en een spreadsheetengine is er buitengewoon aan blootgesteld omdat hij tekstcodering, COM structured storage, compressie en cryptografie raakt in één enkel codepad
Waarom houdt de core vast aan DELPHIUNICODE in plaats van gewoon DELPHI?
Omdat de formula engine afhankelijk is van String en Char met UTF-16-semantiek, en het ANSI-alternatief karakters verliest voordat er überhaupt iets de file bereikt. Het is verleidelijk om de core in FPC DELPHI-modus te bouwen, want dat is de compatibiliteitsswitch waar de meeste ports naar grijpen, en de code compileert gewoon. Dan round-tript een workbook met Chinese sheetnamen of Cyrillische labels door het rekenpad en zijn de karakters weg tegen de tijd dat de writer ze ziet, zonder enige foutmelding
De modus is niet eenvormig door de hele library, en dat is bewust in plaats van slordig. De PNG-bytedecoder en de LCL-overrides hebben echt ANSI-signaturen nodig, want ze werken met bytes en met wat de widgetset hun doorgeeft. Die units zetten een aparte LX_FPC_ANSI-switch aan. Twee modi in één library klinkt als een smell totdat u beseft dat het alternatief een bytedecoder is die zijn invoer als tekst behandelt
Er is een begeleidend detail dat mensen later vangt. DELPHIUNICODE maakt TFormatSettings.DecimalSeparator in de FPC-runtime geen WideChar. Invoer met een Unicode-deimalscheidingsteken moet eerst binnen de Unicode-string genormaliseerd worden naar een ASCII-scheidingsteken, en invoer waarvan het scheidingsteken niet matcht moet worden geweigerd in plaats van stilletjes afgekapt op het teken dat de parser niet herkende
program ExportReport;
{$MODE DELPHI}
uses
Interfaces, // moet eerst komen: initialiseert de LCL-widgetset
SysUtils, lxHandle; // en de UTF-8-conversielaag
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.
De unit Interfaces is niet optioneel en hij moet eerst komen. Hij initialiseert de LCL-widgetset en de UTF-8-conversielaag, en HotXLS is op beide aangewezen zodra fonts, bestandspaden of tekst de RTL- en LCL-grens oversteken. Een consoleprogramma dat hem overslaat, compileert en gedraagt zich verkeerd op elk niet-ASCII-pad. Dat is ook de reden dat een geslaagde compile hier zo weinig bewijst: de port werkte pas aantoonbaar zodra echte documenten met echte fontnamen en echte paden een volledige round trip maakten
Een class VMT is geen COM vtable
Free Pascal laat u niet toe een class VMT aan Windows te geven als COM interface vtable, zelfs niet als de declaratie er identiek uitziet als die welke Delphi accepteert. De layouts verschillen op manieren die een aanroep in de verkeerde slot opleveren, wat zich manifesteert als een crash ergens compleet los van de call site. Structured storage telt hier zwaar omdat het klassieke binaire workbookformaat een OLE compound file is, en er een lezen of schrijven betekent ILockBytes implementeren waar de Windows storage API terugbelt
De werkende constructie is een CORBA-interface met expliciet gedeclareerde COM slots en met de hand beheerde AddRef en Release. Dat betekent automatisch reference counting opgeven voor deze types en de levensduur zelf verantwoorden, een redelijke ruil voor een handjevol interfaces dat binnen één unit leeft. De specifieke val in dat werk is QueryInterface: hij moet een interfacepointer teruggeven, niet de objectpointer. Beide compileren. Eén van beide geeft Windows een adres waarvan het eerste machinewoord geen vtable is
De FPC-specifieke declaraties staan in lxOleInterfaces.inc, naast lxAESBackend.inc en lxZlibBackend.inc in de FPC-source directory, zodat de compilerspecifieke keuzes op één plek zitten in plaats van door de engine verspreid. Het formaat zelf en hoe de library erdoorheen navigeert staat beschreven in OLE2 compound files lezen in Pascal
Nog één typedetail hoort bij dezelfde familie. LargeInt moet in de FPC-tak op Int64 uitkomen, en de compilerclassificatie van Comp verschilt genoeg tussen de twee toolchains dat overload resolution een andere kandidaat kan kiezen. Test gedrag bij grote offsets met een filestream in plaats van een HGLOBAL-stream: de Windows global-memory stream loopt vanzelf om bij seeks voorbij 4 GiB, dus een geslaagde test daar bewijst niets over uw eigen rekenwerk
Wat een self-consistente AES-implementatie verbergt
De Win32 AES-objectbestanden die de Delphi-build linkt zijn OMF, en de Free Pascal-linker kan ze niet verwerken, dus de FPC-tak gebruikt een Pascal AES-implementatie. Delphi linkt gewoon de objectbestanden die het altijd al linkte, wat de uitgebrachte binary ongewijzigd houdt voor bestaande klanten
De verificatie-eis is het deel dat u elk project in moet nemen. Data versleutelen en met dezelfde implementatie weer ontsleutelen bewijst helemaal niets: een symmetrisch algoritme met een verkeerde key schedule, verkeerde blokvolgorde of verkeerde chaining is perfect self-consistent en round-trip zijn eigen output telkens. Alleen known-answer vectors vangen het, door de key expansion, de blokvolgorde en de CBC-chaining tegen gepubliceerde waarden te checken. Schep een self-consistente foute implementatie in, en het symptoom verschijnt zodra een klant de file voor het eerst in Excel opent
Compressie had een defect van ander karakter. Een Pascal inflate-backend kan nog output pending hebben nadat hij alle gecomprimeerde invoer heeft verbruikt, dus de aanroeper moet blijven doorroepen tot de stream zijn einde meldt. Uitgeputte invoer als end-of-stream behandelen kapt het laatste blok af. Erger nog, het maakt van een beschadigd archief een stilletjes geaccepteerd archief, en dat is precies de faalmodus die de hardening in het valideren van het ZIP end-of-central-directory record moet voorkomen. De regel is: geen vooruitgang plus niet klaar is een truncatiefout, nooit een EOF
Twee valkuilen in het buildsysteem die echte uren kosten
De LCL-zoekpaden moeten vóór de FPC-pakket-wildcardpaden staan, anders schaduwt de Free Vision-unit Menus de LCL-unit met dezelfde naam en krijgt u een PPU-checksum-mismatch die over geen van beide iets zegt. Een Lazarus-installatie die na installatie is verplaatst, kan bovendien verouderde paden in fpc.cfg achterlaten, dus de build-entrypoints specificeren unit- en binaire paden expliciet in plaats van wat de omgeving biedt over te nemen
De tweede valkuil heeft niets met Pascal te maken. Een .cmd-batchfile geschreven met LF-regeleinden werkt totdat de file groter wordt dan de leesbuffer van de interpreter, en op dat moment faalt call :label met de melding dat het batchlabel niet bestaat, en de failure verschijnt bij welk programma er ook net voorbij de grens zit. Elke tool die een batchscript herschrijft, moet CRLF terugschrijven. En lazbuild --build-all leegt de unit-outputdirectory van het pakket vóór het compileren, dus een optiebestand dat daar geparkeerd staat wordt gewist voordat het gelezen kan worden: houd het erbuiten, en onthoud dat het @-pad relatief aan de pakketdirectory wordt resolved omdat lazbuild de compiler vanaf daar aanroept
// Lazarus grid-export: TGridToXLS wordt meegeleverd in het Lazarus-pakket,
// dus dezelfde DB-grid-exportcode werkt in een LCL-applicatie
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;
Wat een compilerwaarschuwing waard is
Free Pascal rapporteert ongeïnitialiseerde lokale variabelen die Delphi niet meldt, en het draaien van de FPC-build veranderde dat verschil in twee echte defecten in de calculatie-unit. Eén functie las een tellervariabele die nooit was toegewezen voordat hij werd gebruikt, en een andere gebruikte twee coördinaten in de ene tak voordat de code die ze berekende in een andere tak draaide. Onder Delphi gedroegen beide zich naar wat de stack toevallig bevatte, en dat is per definitie een bug die op de ene machine reproduceert en op de andere niet
De praktische conclusie is dat de tweede compiler het waard is om in de loop te blijven, zelfs voor een product dat vooral op de eerste verschijnt. De FPC-waarschuwingsklassen periodiek afscannen is een goedkope statische analysepas over een Delphi-codebase, en hij vindt een categorie defecten die geen enkele testsuite betrouwbaar bereikt. De bredere versiematrix-discipline waar dit binnen valt, staat beschreven in de cross-compiler build matrix
Free Pascal- en Lazarus-ondersteuning voor Windows wordt geleverd met de HotXLS Delphi spreadsheet component als Lazarus-pakket naast de Delphi- en C++Builder-pakketten, gebouwd uit dezelfde source tree in plaats van een fork. Dat is de hele pointe: één engine, vier toolchains, en de compilerspecifieke beslissingen geïsoleerd in include files die u in één zitting kunt nalezen