HotXLS bygger under Free Pascal och Lazarus på Windows, och porteringen vilade på fyra beslut som inte har något med Object Pascal-syntax att göra: hålla kärnan i DELPHIUNICODE-läge, deklarera OLE:s structured-storage-interface som CORBA-interface med handhanterad referensräkning, ersätta Win32-AES-objektfilerna med en Pascal-implementation, och fixa en inflate-loop som kunde acceptera en trunkerad ZIP som komplett
Vem som helst som portat ett moget Delphi-bibliotek känner formen på det här arbetet. Kompilatorn accepterar nästan allt i första passet. Det som följer är en lång svans av beteendeskillnader som kompilerar rent och producerar fel resultat, och en kalkylbladsmotor är ovanligt utsatt för dem eftersom den rör textencoding, COM structured storage, komprimering och kryptografi i en enda kodväg
Varför insisterar kärnan på DELPHIUNICODE i stället för ren DELPHI?
Därför att formelmotorn beror på att String och Char bär UTF-16-semantik, och ANSI-alternativet tappar tecken innan något når filen. Det är frestande att bygga kärnan i FPC:s DELPHI-läge, eftersom det är kompatibilitetsomkopplaren de flesta porteringar sträcker sig efter, och koden kompilerar. Sedan round-trippar en arbetsbok med kinesiska arknamn eller kyrilliska etiketter genom beräkningsvägen och tecknen är borta när skrivaren ser dem, utan något fel någonstans
Läget är inte enhetligt över biblioteket, och det är medvetet snarare än ostrukturerat. PNG-bytedecodern och LCL-överskrivningarna behöver genuint ANSI-signaturer, för de sysslar med byte och med vad widgetseten lämnar dem. De enheterna slår på en separat LX_FPC_ANSI-switch. Två lägen i ett bibliotek låter som en designlukt tills du märker att alternativet är en bytedecoder som behandlar sin indata som text
Det finns en följeslagardetalj som fångar folk senare. DELPHIUNICODE gör inte TFormatSettings.DecimalSeparator till en WideChar i FPC-runtimen. Indata som bär en Unicode-decimalseparator måste normaliseras till en ASCII-separator inuti Unicode-strängen först, och indata vars separator inte matchar den förväntade måste avvisas i stället för att tyst trunkeras vid tecknet parsaren inte kände igen
program ExportReport;
{$MODE DELPHI}
uses
Interfaces, // måste komma först: initierar LCL-widgetseten
SysUtils, lxHandle; // samt UTF-8-konverteringslagret
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.
Enheten Interfaces är inte valfri och den måste komma först. Det är den som initierar LCL-widgetseten och UTF-8-konverteringslagret, och HotXLS förlitar sig på båda så snart typsnitt, filsökvägar eller text korsar RTL- och LCL-gränsen. Ett konsolprogram som hoppar över den kommer att kompileras och missköta sig på vilken icke-ASCII-sökväg som helst. Det är också skälet till att en lyckad kompilering bevisar så lite här: porteringen var först påvisbart fungerande när riktiga dokument med riktiga typsnittsnamn och riktiga sökvägar gjorde en fullständig rundresa
En klass-VMT är inte en COM-vtable
Free Pascal låter dig inte lämna en klass-VMT till Windows som en COM-interface-vtable, ens när deklarationen ser identisk ut med den Delphi accepterar. Layouterna skiljer sig på sätt som ger ett anrop till fel plats, vilket manifesteras som en krasch någonstans orelaterat till anropsplatsen. Structured storage spelar roll här för att det klassiska binära arbetsboksformatet är en OLE-kompilfil, och att läsa eller skriva en sådan betyder att implementera ILockBytes som Windows storage-API anropar tillbaka in i
Det fungerande upplägget är ett CORBA-interface med COM-platserna deklarerade explicit och AddRef och Release hanterade för hand. Det betyder att ge upp automatisk referensräkning för dessa typer och ta ansvar för livstiden, vilket är en rimlig handel för en handfull interface som lever inuti en enda enhet. Den specifika fällan i det arbetet är QueryInterface: den måste returnera en interface-pekare, inte objektpekaren. Båda kompilerar. Den ena lämnar Windows en adress vars första maskinord inte är en vtable
De FPC-specifika deklarationerna bor i lxOleInterfaces.inc, jämte lxAESBackend.inc och lxZlibBackend.inc i FPC-källkatalogen, så de kompilatorspecifika valen sitter på ett ställe i stället för att vara utspridda genom motorn. Formatet självt och hur biblioteket navigerar det beskrivs i att läsa OLE2-kompilfiler i Pascal
En typesdetalj till hör till samma familj. LargeInt måste lösa till Int64 i FPC-grenen, och kompilatorns klassificering av Comp skiljer tillräckligt mellan de två verktygskedjorna för att overload-upplösning kan välja en annan kandidat. Testa beteende vid stora offset med en filström i stället för en HGLOBAL-ström: Windows globalminnesström wrappar runt vid seeks förbi 4 GiB av sig själv, så ett passerande test där bevisar ingenting om din egen aritmetik
Vad en självkonsistent AES-implementation gömmer
Win32-AES-objektfilerna som Delphi-bygget länkar är OMF, och Free Pascal-länkaren kan inte konsumera dem, så FPC-grenen använder en Pascal-AES-implementation i stället. Delphi fortsätter att länka de objektfiler den alltid gjort, vilket håller den utlämnade binären oförändrad för befintliga kunder
Verifieringskravet är delen värd att bära med sig till varje projekt. Att kryptera data och dekryptera den igen med samma implementation bevisar ingenting alls: en symmetrisk algoritm med felaktigt nyckelschema, felaktig blockordning eller felaktig chaining är perfekt självkonsistent och round-trippar sitt eget utdata varje gång. Endast kända-svar-vektorer fångar det, genom att kontrollera nyckelutvidgning, blockordning och CBC-chaining mot publicerade värden. Lämna ut en självkonsistent felaktig implementation och symptomet dyker upp första gången en kund öppnar filen i Excel
Komprimeringen hade en defekt av ett annat slag. En Pascal-inflate-backend kan fortfarande ha utdata kvar efter att den konsumerat all sin komprimerade indata, så anroparen måste fortsätta anropa tills strömmen rapporterar sitt slut. Att behandla uttömd indata som strömslut trunkerar det sista blocket. Värre, det förvandlar ett skadat arkiv till ett tyst accepterat, vilket är exakt det fellivet härdningen i validering av ZIP:s end-of-central-directory-post finns för att förebygga. Regeln är att inget framsteg plus inte färdig är ett trunkeringsfel, aldrig en EOF
Två byggsystemsfällor som kostade riktiga timmar
LCL-sökvägarna måste föregå FPC-paketets wildcardsökvägar, annars skuggar Free Vision-enheten Menus LCL-enheten med samma namn och du får en PPU-kontrollsummamismatch som inte säger något om någon av dem. En Lazarus-installation som flyttats efter installationen kan också lämna inaktuella sökvägar i fpc.cfg, så byggningångspunkterna anger enhets- och binärsökvägar explicit i stället för att ärva vadhelst miljön erbjuder
Den andra fällan har ingenting med Pascal att göra. En .cmd-batchfil skriven med LF-radslut fungerar tills filen växer förbi storleken på tolkens läsbuffert, varvid call :label fallerar med påståendet att batchetiketten inte finns, och felet uppträder vid vilket program som råkar sitta förbi gränsen. Vartenda verktyg som skriver om ett batchskript måste skriva tillbaka CRLF. Och lazbuild --build-all rensar paketets enhetsutdatakatalog innan kompileringen, så en optionsfil parkerad i den katalogen raderas innan den kan läsas: håll den utanför, och kom ihåg att @-sökvägen löses relativt paketkatalogen eftersom lazbuild anropar kompilatorn därifrån
// Lazarus-gridexport: TGridToXLS medföljer Lazarus-paketet, så samma
// DB-grid-exportkod fungerar 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;
Vad en kompilatorvarning är värd
Free Pascal rapporterar oinitierade lokala variabler som Delphi inte gör, och att köra FPC-bygget förvandlade den skillnaden till två verkliga defekter i beräkningsenheten. En funktion läste en räknarvariabel som aldrig tilldelats före användning, och en annan använde två koordinater i en gren innan koden som beräknade dem kördes i en annan gren. Under Delphi betedde båda sig enligt vad stacken råkade hålla, vilket är definitionen av en bugg som reproduceras på en maskin och inte på en annan
Den praktiska slutsatsen är att den andra kompilatorn är värd att hålla i loopen även för en produkt som främst levereras på den första. Att skanna FPC:s varningsklasser periodiskt är ett billigt statiskt analyspass över en Delphi-kodbas, och det hittar en kategori av defekter som ingen testsvit pålitligt når. Den bredare versionsmatrisdisciplin detta sitter inom beskrivs i den korskompilatorbyggmatrisen
Free Pascal- och Lazarus-stöd för Windows medföljer HotXLS Delphi spreadsheet component som ett Lazarus-paket vid sidan av Delphi- och C++Builder-paketen, byggt från samma källträd i stället för en fork. Det är poängen med övningen: en motor, fyra verktygskedjor, och de kompilatorspecifika besluten isolerade i include-filer där de kan läsas i ett enda svep