HotXLS bygger under Free Pascal og Lazarus på Windows, og porteringen hang på fire beslutninger, der intet har med Object Pascal-syntaks at gøre: holde kernen i DELPHIUNICODE-mode, deklarere OLE structured-storage-interfacene som CORBA interfaces med håndteret reference counting, erstatte Win32 AES-objektfilerne med en Pascal-implementation og fixe et inflate-loop, der kunne acceptere en afkortet ZIP som komplet
Alle, der har porteret et modent Delphi-bibliotek, kender formen af dette arbejde. Compileren accepterer næsten alt i første gennemløb. Det, der følger, er en lang hale af adfærdsforskelle, der kompilerer rent og producerer forkerte resultater, og en spreadsheet-motor er usædvanligt udsat for dem, for den rører tekstkodning, COM structured storage, komprimering og kryptografi i én enkelt kodevej
Hvorfor insisterer kernen på DELPHIUNICODE frem for almindelig DELPHI?
Fordi formelmotoren afhænger af, at String og Char bærer UTF-16-semantik, og ANSI-alternativet mister tegn, før noget når filen. Det er fristende at bygge kernen i FPC DELPHI-mode, for det er kompatibilitets-switchen, de fleste porter rækker efter, og koden kompilerer. Så round-tripper en projektmappe med kinesiske arkenavne eller kyrilliske labels gennem beregningsvejen, og tegnene er væk, inden writeren ser dem, uden nogen fejl noget sted
Moden er ikke ensartet på tværs af biblioteket, og det er bevidst snarere end uordnet. PNG byte-decoderen og LCL-override-ne behøver reelt ANSI-signaturer, for de handler om byte og om det, widgetset giver dem. De unitter slår en separat LX_FPC_ANSI-switch til. To modes i ét bibliotek lyder som et smell, indtil du bemærker, at alternativet er en byte-decoder, der behandler sit input som tekst
Der er en ledsagende detalje, der fanger folk senere. DELPHIUNICODE gør ikke TFormatSettings.DecimalSeparator til en WideChar i FPC-runtime. Input med en Unicode-decimalseparator skal normaliseres til en ASCII-separator inde i Unicode-strengen først, og input, hvis separator ikke matcher den forventede, skal afvises snarere end lydløst afkortes ved det tegn, parseren ikke genkendte
program ExportReport;
{$MODE DELPHI}
uses
Interfaces, // skal komme først: initialiserer LCL widgetset
SysUtils, lxHandle; // samt UTF-8-konverteringslaget
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-uniten er ikke valgfri, og den skal komme først. Den er det, der initialiserer LCL widgetset og UTF-8-konverteringslaget, og HotXLS stoler på begge, så snart fonts, filstier eller tekst krydser RTL- og LCL-grænsen. Et konsolprogram, der skipper den, kompilerer og opfører sig forkert på enhver ikke-ASCII-sti. Det er også grunden til, at en vellykket kompilering beviser så lidt her: porteringen virkede først påviseligt, da rigtige dokumenter med rigtige fontnavne og rigtige stier lavede en fuld round trip
En class VMT er ikke en COM vtable
Free Pascal lader dig ikke give en class VMT til Windows som en COM interface vtable, selv når deklarationen ligner den, Delphi accepterer, til forveksling. Layoutene adskiller sig på måder, der giver et kald ind i den forkerte slot, hvilket manifesterer sig som et crash et sted, der er urelateret til call site. Structured storage betyder noget her, for det klassiske binære projektmappeformat er en OLE compound file, og at læse eller skrive én betyder at implementere ILockBytes, som Windows storage API vil kalde tilbage i
Ordningen, der virker, er et CORBA interface med COM slots deklareret eksplicit og AddRef og Release håndteret i hånden. Det betyder at give afkald på automatisk reference counting for disse typer og tage ansvar for levetiden, hvilket er en fair handel for en håndfuld interfaces, der bor inde i én unit. Den specifikke fælde inde i det arbejde er QueryInterface: den skal returnere en interface-pointer, ikke objekt-pointeren. Begge kompilerer. Den ene af dem giver Windows en adresse, hvis første maskinord ikke er en vtable
FPC-specifikke deklarationer bor i lxOleInterfaces.inc, ved siden af lxAESBackend.inc og lxZlibBackend.inc i FPC source-mappen, så de compilerspecifikke valg ligger ét sted i stedet for at være spredt gennem motoren. Formatet selv, og hvordan biblioteket navigerer det, er beskrevet i læsning af OLE2 compound files i Pascal
Én mere typedetalje hører til samme familie. LargeInt skal resolve til Int64 i FPC-grenen, og compilerklassifikationen af Comp adskiller sig nok mellem de to toolchains til, at overload resolution kan vælge en anden kandidat. Test large-offset-adfærd med en file stream snarere end en HGLOBAL stream: Windows global-memory stream wrapper rundt ved seeks forbi 4 GiB af sig selv, så en bestået test dér beviser intet om din egen aritmetik
Hvad en selvkonsekvent AES-implementation skjuler
Win32 AES-objektfilerne, som Delphi-builden linker, er OMF, og Free Pascal-linkeren kan ikke indtage dem, så FPC-grenen bruger en Pascal AES-implementation i stedet. Delphi fortsætter med at linke de objektfiler, den altid gjorde, hvilket holder den udgivne binary uændret for eksisterende kunder
Verificeringskravet er den del, der er værd at tage med til ethvert projekt. Krypterer man data og dekrypterer dem igen med samme implementation, beviser det slet ingenting: en symmetrisk algoritme med en forkert key schedule, forkert blokorden eller forkert chaining er perfekt selvkonsekvent og round-tripper sit eget output hver gang. Kun known-answer vectors fanger den, ved at tjekke key expansion, blokorden og CBC chaining mod publicerede værdier. Shipper man en selvkonsekvent forkert implementation, viser symptomet sig første gang, en kunde åbner filen i Excel
Komprimering havde en defekt af en anden karakter. En Pascal inflate-backend kan stadig have output pending, efter den har indtaget alt sit komprimerede input, så kalderen skal blive ved med at kalde, til streamen melder sin ende. At behandle udtømt input som end-of-stream afkorter den sidste blok. Værre, det forvandler et beskadiget arkiv til et lydløst accepteret, hvilket netop er den fejlmåde, hardening i validering af ZIP end-of-central-directory-recorden findes for at forhindre. Reglen er, at ingen fremgang plus ikke færdig er en afkortningsfejl, aldrig en EOF
To build-system-fælder, der kostede rigtige timer
LCL search paths skal ligge forud for FPC package wildcard-stierne, ellers skygger Free Vision Menus-uniten for LCL-uniten af samme navn, og du får et PPU checksum mismatch, der intet siger om nogen af dem. En Lazarus-installation, der blev flyttet efter installationen, kan også efterlade forældede stier i fpc.cfg, så build-indgangspunkterne specificerer unit- og binærstier eksplicit i stedet for at arve, hvad end miljøet tilbyder
Den anden fælde intet har med Pascal at gøre. En .cmd-batchfil skrevet med LF-linjeender virker, til filen vokser forbi fortolkerens read buffer-størrelse, hvorpå call :label fejler med påstanden om, at batchlabelen ikke findes, og fejlen optræder hos det program, der tilfældigvis ligger forbi grænsen. Ethvert værktøj, der omskriver et batchscript, må skrive CRLF tilbage. Og lazbuild --build-all rydder package unit output-mappen før kompilering, så en options-fil parkeret i den mappe slettes, før den kan læses: hold den udenfor, og husk, at @-stien resolves relativt til package-mappen, for lazbuild kalder compileren derfra
// Lazarus grid-eksport: TGridToXLS følger med i Lazarus-pakken, så den
// samme DB-grid-eksportkode virker i en LCL-applikation
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;
Hvad en compiler-warning er værd
Free Pascal melder uinitialiserede lokale variabler, som Delphi ikke gør, og at køre FPC-builden forvandlede den forskel til to rigtige defekter i beregningsuniten. Én funktion læste en count-variabel, der aldrig blev tildelt før brug, og en anden brugte to koordinater i én gren, før koden, der beregnede dem, kørte i en anden gren. Under Delphi opførte begge sig efter, hvad stakken tilfældigvis holdt, hvilket er definitionen på en bug, der reproducerer på én maskine og ikke på en anden
Den praktiske konklusion er, at den anden compiler er værd at beholde i løkken, selv for et produkt, der primært shipper på den første. At skanne FPC warning-klasserne periodisk er et billigt statisk analysepass over en Delphi kodebase, og det finder en kategori af defekt, som ingen test suite pålideligt når. Den bredere versionsmatrix-disciplin, dette sidder inde i, er beskrevet i cross-compiler build matrix
Free Pascal- og Lazarus-understøttelse af Windows følger med HotXLS Delphi spreadsheet-komponenten som en Lazarus-pakke ved siden af Delphi- og C++Builder-pakkerne, bygget fra samme source tree snarere end en fork. Det er hele pointen med øvelsen: én motor, fire toolchains og de compilerspecifikke beslutninger isoleret i include-filer, hvor de kan læses i én sidning