HotPDF се компилира и изпълнява под Free Pascal 3.2.2 с Lazarus, а честното резюме на този пренос са две изречения; Създаване на документи, зареждане, записване, компресия, декомпресия, шифроване и дешифриране всички работят върху само-Pascal бекендове, така че Lazarus приложение може да произвежда и консумира истински PDF без каквито и да е C зависимости; Опционалните нативни image кодеци не, защото предварително компилираните Win64 обекти използват COFF вариант, който нито един Free Pascal линкер не може да консумира, така че на тази toolchain входните точки резолвират към заглушки, които се провалят затворено
Преминаването от „компилира се“ към „работи“ отне специфичен набор от поправки и всяка една от тях е капан, който ще хване всеки друг Delphi codebase, движещ се към Free Pascal; Те си заслужават да се запишат в реда, в който болят
Защо компилиращ модул не доказва нищо?
Защото Pascal модул може да реферира символ, който никога няма да направи нищо полезно и все пак да задоволи компилатора; В момента, в който всичките 113 библиотечни модула се компилираха чисто под Free Pascal, обработчиците на архивни контейнери наистина работеха, проверени със smoke тест, който отвори CBZ и го превърна в PDF; XFA form flattening изобщо не работеше, защото flattening трябва да inflate-не компресирания /XFA packet поток, а deflate входната точка все още беше заглушка; Нищо в build изхода не разграничи тези два случая
Правилото, излязло оттам, е кратко; Преди да напишете в release бележка, че функция работи на нова toolchain, напишете runtime сонда, която упражнява функцията от край до край на тази toolchain; Компилаторно покритие е предпоставка, никога доказателство; По-широката картина на това, което преносът покрива, е в бележките за поддръжка на Free Pascal и Lazarus Win64
Raise вътре в cdecl заглушка не достига извикващия
Това заслужава собствена секция, защото симптомът е толкова подвеждащ; Заглушечните модули излагат C входни точки така, както би направила статична библиотека, така че заглушка изглежда така
// Изглежда разумно. Не е.
function inflate(Strm: Pointer; Flush: Integer): PtrUInt; cdecl;
public name 'inflate';
begin
raise ENotSupportedException.Create('codec unavailable');
end;
На Free Pascal за Win64 това изключение не се разпространява до извикващия; Няма try..except обработчик, който да го види, защото unwinding през cdecl граница, декларирана по този начин, не носи Pascal exception рамката; процесът се терминира с exit код 217; От страна на приложението няма грешка, няма съобщение и няма log ред, само програма, която изчезва; Това е строго по-лошо от грешен отговор, защото грешен отговор може да се обработи
Съблазнителната поправка е заглушката вместо това да връща код за отказ и за inflate това е правилно, защото zlib има добре дефиниран error return; Тя е грешна изобщо: заглушка за jpeg_read_header, която връща нула, казва на извикващия да продължи със структура, която никой не е инициализирал; Трайната поправка е да се огради на Pascal входната точка, а не вътре в C-образната заглушка, използвайки каквата конвенция за отказ API-то вече има
function TryDecodeJPEG(const Data: TBytes; out Bitmap: TBitmap): Boolean;
begin
{$IFDEF FPC}
// Отказ, преди заглушката изобщо да бъде достигната, със собствена
// конвенция за отказ на това API, а не изключение през cdecl
Bitmap := nil;
Result := False;
Exit;
{$ENDIF}
Result := DecodeJPEGNative(Data, Bitmap);
end;
paszlib не е zlib, а разликата са два класа документи
Pascal deflate имплементацията, налична на Free Pascal, обработва две framings: zlib wrapper-а и raw deflate; Тя не обработва gzip framing, който zlib избира чрез windowBits стойности от 16 до 31, и не обработва режима за автоматично откриване, който стойностите 32 до 47 избират; HotPDF се нуждае и от двете; Безопасният SVG импорт път иска 31, а loader-ът има стълба от fallback-и, която иска 47, когато framing-ът на поток е неясен; Пропуснете едното и цяло семейство документи спира да се отваря, с decode грешка, сочеща към потока, а не към липсващия framing
Има втора, по-остра несъвместимост; z_stream записът, който paszlib декларира, няма същото memory оформление като C такова: неговото поле msg е short string, а не указател, а total_in и total_out са 64-битови, докато C ABI има машинни думи; Запис на извикващия следователно не може да бъде подаван направо; Работният уред е paszlib състоянието да стои зад указателя state, който публичният запис вече резервира, и публичните полета да се копират в и из около всяко извикване; Gzip CRC и осембайтовия length трейлър се отчитат в същия shim слой, който е естественото място за тях, тъй като той вече притежава решението за framing
Подаване на динамичен масив на нетипизиран var параметър
Това е дефектът, който най-вероятно седи във вашия код в момента; Когато подадете динамичен масив на нетипизиран var параметър, това, което извиканият получава, е адресът на масивната променлива, който е адресът на указател, не адресът на payload-а; Така четене в него презаписва самата променлива и каквото седне до нея
var
FBuffer: TBytes;
begin
SetLength(FBuffer, 65536);
// Грешно: предава адреса на променливата FBuffer
FStream.Read(FBuffer, Length(FBuffer));
// Правилно: предава адреса на първия payload байт
FStream.Read(FBuffer[0], Length(FBuffer));
end;
На Delphi грешната форма често изглежда работи, защото това, което тя поврежда, е съседен stack слот, който нищо не чете после; На Free Pascal същият ред segmentation-fault при първа употреба; Това, което го прави толкова трудно за забелязване с око, е, че статичните масиви нямат такъв проблем, тъй като статична масивна променлива е собственът ѝ payload, така че и двете изписвания са правилни в един и същ файл в зависимост от декларацията няколкостотин реда по-далеч
ZIP контейнери без System.Zip
Free Pascal няма еквивалент на RTL zip модула, а наличната алтернатива има едновременно различна API повърхност и никаква поддръжка на legacy шифроването, което по-старите контейнерни формати все още използват, така че малък четец в библиотеката се оказа по-кратък от приспособяването към нея; Два детайла на формата струваха време и са лесни за объркване
Първият е check байтът на encryption header-а; Неговият дванайсети байт обикновено е старшият байт на CRC, но когато general-purpose флаг бит 3 е установен, което значи, че размерите живеят в trailing data descriptor и CRC все още не е известен, check байтът идва от старшия байт на времето на модификация вместо това; Имплементирайте само CRC формата и всеки архив, записан в streaming режим, отхвърля правилна парола; Вторият е ZIP64 extra полето: неговите три 64-битови полета се появяват в фиксиран ред, но се записват само когато съответното 32-битово поле е наситено, така че четенето им на фиксирани offsets работи върху архивите, които сте тествали, и се проваля на следващия; Парсвайте ги позиционно спрямо кои 32-битови полета са наситени
Едно удобство, което си заслужава да се знае: Free Pascal декомпресионният поток приема втори конструктор аргумент, който прескача zlib header-а, което е точно това, от което ZIP записите се нуждаят, тъй като съхраняват raw deflate; Този път изобщо не докосва library zlib shim-а, така че не е засегнат от липсващия C бекенд
Прозрачност на цветни глифи под LCL
Четенето на alpha канала на растеризиран цветен глиф е единственият graphics детайл без директен превод; LCL PNG класът няма scanline accessor, който излага alpha, а присвояването на PNG към bitmap го изхвърля, така че цветен emoji пристига напълно непрозрачен и се композира с черна кутия зад него; Работният маршрут е interface изображението: създайте го от PNG, после четете пиксели през цветния accessor, помнейки, че неговите компоненти са 16-битови и се нуждаят от изместване надолу с осем, за да станат байтове; Тази повърхност също използва естествен ред на редовете отгоре надолу, така че инверсията Height - 1 - Y, от която VCL scanline код се нуждае, трябва да бъде премахната, а не пренесена
Две бележки за build системата, преди да докладвате дефект
Пълно прекомпилиране от време на време се проваля с недефиниран символ, чието име завършва със суфикс $crc и hex стойност; Този суфикс се изчислява от типовете параметри и се проваля да съвпадне, когато една компилация компилира модул срещу две различни interface версии в един и същ проход; Повторното изпълнение на компилацията го изчиства; сигнатурата не е грешна
Второ, Free Pascal 3.2.2 няма анонимни методи, така че навсякъде, където библиотеката е използвала closures за свързване на паралелен конвейер, Free Pascal компилацията взема детерминистичен сериен fallback вместо това; Изходът е идентичен, пропускателната способност не е; ако зависите от паралелно изобразяване на страници, това е причина да останете на Delphi за момента, а дизайнът на конвейера е описан в статията за паралелен render конвейер; Ситуацията с image кодеците е другото място, където изборът на toolchain променя възможностите, а не само скоростта, така че Lazarus внедряване трябва да планира своите image формати съответно; текущата матрица per-toolchain е на продуктовата страница HotPDF Delphi PDF component