HotXLS се компилира под Free Pascal и Lazarus на Windows, а портът се въртеше около четири решения, които нямат нищо общо с Object Pascal синтаксиса: ядрото остава в DELPHIUNICODE режим, OLE structured-storage интерфейсите се обявяват като CORBA интерфейси с ръчно управляван reference counting, Win32 AES обектните файлове се заменят с Pascal имплементация, и inflate цикъл, способен да приеме отрязан ZIP за завършен, се поправя
Всеки, портирал зряла Delphi библиотека, познава формата на тази работа. Компилаторът приема почти всичко на първия пас. Следва дълга опашка от поведенчески разлики, които се компилират чисто и произвеждат грешни резултати, а spreadsheet двигател е необичайно изложен на тях, защото пипа текстово кодиране, COM structured storage, компресия и криптография в един кодов път
Защо ядрото настоява за DELPHIUNICODE, а не за обикновен DELPHI?
Защото формулният двигател разчита String и Char да носят UTF-16 семантика, а ANSI алтернативата губи символи, преди нещо да стигне файла. Изкушаващо е да построиш ядрото в FPC DELPHI режим, тъй като това е compatibility превключвателят, към който повечето портове посягат, и кодът се компилира. После workbook с китайски имена на sheets или кирилоски етикети мине през изчислителния път и символите са изчезнали, докато writer-ът ги види, без нито една грешка някъде
Режимът не е еднакъв из цялата библиотека и това е нарочно, а не разхвърляно. PNG байтовият декодер и LCL override-ите истински се нуждаят от ANSI сигнатури, защото се занимават с байтове и с това, което widgetset-ът им подава. Тези unit-и включват отделен LX_FPC_ANSI ключ. Два режима в една библиотека звучи като code smell, докато не забележите, че алтернативата е байтов декодер, третиращ входа си като текст
Има придружаващ детайл, който хваща хората после. DELPHIUNICODE не прави TFormatSettings.DecimalSeparator на WideChar в FPC runtime-а. Вход, носещ Unicode десетичен разделител, трябва да бъде нормализиран към ASCII разделител в Unicode низа първо, а вход, чийто разделител не пасва на очаквания, трябва да бъде отхвърлян, вместо безшумно подрязан на символа, който парсерът не е разпознал
program ExportReport;
{$MODE DELPHI}
uses
Interfaces, // трябва да е пръв: инициализира LCL widgetset-а
SysUtils, lxHandle; // и UTF-8 конверсионния слой
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.
Unit-ът Interfaces не е по избор и трябва да е пръв. Той инициализира LCL widgetset-а и UTF-8 конверсионния слой, а HotXLS разчита на и двата щом шрифтове, файлови пътища или текст пресекат границата RTL-LCL. Конзолна програма, прескочила го, ще се компилира и ще се държи зле на всеки не-ASCII път. Това е и причината успешна компилация да доказва толкова малко тук: портът демонстративно работеше едва щом истински документи с истински имена на шрифтове и истински пътища направиха пълен кръг
Class VMT не е COM vtable
Free Pascal няма да ви позволи да подадете class VMT на Windows като COM interface vtable, дори когато декларацията изглежда идентична с тази, която Delphi приема. Подредбите се различават по начини, произвеждащи извикване в грешен слот, което се проявява като краш някъде, несвързан с мястото на извикването. Structured storage има значение тук, защото класическият binary workbook формат е OLE compound file, а четенето или писането на такъв значи имплементиране на ILockBytes, в който Windows storage API ще се обажда обратно
Работната уредба е CORBA интерфейс с COM слотовете обявени изрично и AddRef и Release управлявани на ръка. Това значи да се откажете от автоматичния reference counting за тези типове и да поемете отговорността за живота им, което е честен компромис за шепа интерфейси, живеещи в един unit. Конкретният капан в тази работа е QueryInterface: трябва да върне интерфейсен указател, не указател към обекта. И двете се компилират. Едното подава на Windows адрес, чиято първа машинна дума не е vtable
FPC-специфичните декларации живеят в lxOleInterfaces.inc, до lxAESBackend.inc и lxZlibBackend.inc в FPC source директорията, така че специфичните за компилатора избори седят на едно място, а не са разпръснати из двигателя. Самият формат и как библиотеката се движи из него са описани в четене на OLE2 compound файлове в Pascal
Още един типове детайл принадлежи на същото семейство. LargeInt трябва да се разреши до Int64 в FPC клона, а класификацията на Comp от компилатора се различава достатъчно между двата toolchain-а, че overload разрешаването може да избере различен кандидат. Тествайте поведението при големи офсети с file stream, а не с HGLOBAL stream: Windows global-memory stream-ът се превърта сам на seek отвъд 4 GiB, така че минаващ тест там не доказва нищо за вашата аритметика
Какво крие самосъгласувана AES имплементация
Win32 AES обектните файлове, които Delphi build-ът свързва, са OMF, а Free Pascal linker-ът не може да ги смълче, така че FPC клонът ползва Pascal AES имплементация. Delphi продължава да свързва обектните файлове, както винаги, което пази пуснатия binary непроменен за съществуващите клиенти
Изискването за верификация е частта, която си струва да понесете във всеки проект. Кодиране на данни и декодиране им отново с една и съща имплементация не доказва нищо: симетричен алгоритъм с грешен key schedule, грешен блоков ред или грешно свързване е перфектно самосъгласуван и ще мине кръг-обратно собствения си изход всеки път. Само known-answer векторите го хващат, проверявайки key разширението, блоковия ред и CBC свързването срещу публикувани стойности. Пуснете самосъгласувана грешна имплементация и симптомът се появява първия път, когато клиент отвори файла в Excel
Компресията имаше дефект с друг характер. Pascal inflate backend може още да има изход в изчакване, след като е изсмукал целия си компресиран вход, така че викащият трябва да продължава да вика, докато stream-ът докладва края си. Третирането на свършен вход като край на stream подрязва последния блок. По-лошо – превръща повреден архив в тихо приет, което е точно повредовият модел, който закалението в валидиране на ZIP end-of-central-directory записа съществува да пази. Правилото е, че никакъв прогрес плюс не-завършено е грешка от подрязване, никога EOF
Два build-system капана, струващи истински часове
LCL search пътищата трябва да предхождат wildcard пътищата на FPC пакетите, иначе Free Vision Menus unit-ът засенчва LCL unit-а със същото име и получавате PPU checksum несъответствие, което не казва нищо за нито едната. Инсталация на Lazarus, преместена след инсталирането, може и да остави застояли пътища в fpc.cfg, така че build входните точки задават unit и binary пътища изрично, вместо да наследяват каквото средата предложи
Вторият капан няма нищо общо с Pascal. .cmd batch файл, писан с LF краища на редове, работи, докато файлът порасне отвъд размера на read буфера на интерпретатора, в който момент call :label се проваля с твърдение, че batch label-ът не съществува, а провалът се появява при което и да е място, седящо отвъд границата. Всеки инструмент, пренаписващ batch скрипт, трябва да записва CRLF обратно. И lazbuild --build-all изчиства output директорията на package unit-ите преди компилация, така че options файл, паркиран в тази директория, се изтрива преди да бъде прочетен: държете го извън нея и помнете, че @ пътят се разрешава относително спрямо package директорията, защото lazbuild вика компилатора оттам
// Lazarus grid export: TGridToXLS ship-ва в Lazarus пакета, така че
// същият DB-grid export код работи в LCL приложение
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;
Колко струва предупреждение от компилатора
Free Pascal докладва неинициализирани локални променливи, които Delphi не докладва, и пускането на FPC build-а превърна тази разлика в два реални дефекта в изчислителния unit. Една функция четеше броячна променлива, никога не присвоявана преди употреба, а друга ползваше две координати в един клон преди кодът, смятащ ги, да е тръгнал в друг клон. Под Delphi и двете се държаха според каквото стекът случайно държеше, което е дефиницията на бъг, репродуциращ се на една машина, но не на друга
Практическото заключение е, че вторият компилатор си заслужава да остане в цикъла дори за продукт, ship-ващ преди всичко на първия. Скан на FPC warning класовете периодично е евтин static analysis пас върху Delphi кодова база, и намира категория дефекти, до които никаква test suite надеждно не стига. По-широката дисциплина на version matrix, в която това седи, е описана в cross-компилаторната build матрица
Free Pascal и Lazarus поддръжката за Windows ship-ва с HotXLS Delphi spreadsheet компонента като Lazarus пакет редом с Delphi и C++Builder пакетите, build-нат от същото source дърво, а не от fork. Това е смисълът на упражнението: един двигател, четири toolchain-а, и специфичните за компилатора решения, изолирани в include файлове, които могат да бъдат прочетени на едно заседание