PDFium компонентът намира своята нативна библиотека чрез фиксирана, подредена верига за търсене, вместо да го остави на loader-а на операционната система, защото deployment дърво, което е изрично, е deployment дърво, което можете да дебъгвате; На Windows тази верига търси поддиректория Win32 или Win64, която инсталаторът вече доставя; На други цели тя изгражда името на поддиректорията от Free Pascal target макросите, като <cpu>-<os>, така че deployment дървото се чете точно като дървото на компилираните модули; Това последно решение въведе дефект, който си заслужава цялата статия, защото причината беше главна буква, а симптомът беше мълчание
Веригата, по ред
Четири местоположения, опитвани в последователност, после platform loader-ът като последна инстанция; Първо предпочитаното оформление, директория DLLs до изпълнимия файл, съдържаща по една поддиректория за цел; Второ алтернативно оформление с target поддиректорията директно до изпълнимия файл; Трето плоското legacy оформление, библиотеката седяща до изпълнимия файл без изобщо поддиректория; Четвърто, само на Windows, системната директория, която се нуждае от грижа, защото 32-битов процес трябва да гледа в SysWOW64 и 64-битов процес в System32 и на 32-битов Windows първата не съществува, така че търсенето трябва да падне обратно; Едва след всичко това loader-ът бива помолен да търси сам
Нарочно няма стъпка за системна директория извън Windows; Собственият път за търсене на platform loader-а, воден от конфигурацията на runtime linker и library path средата, вече покрива тази територия и дублирането му в Pascal би означавало преимплементиране на правила, които варират по дистрибуция; Диагностицирането на откази във Windows веригата е разгледано отделно в deploy на PDFium DLL и диагностициране на откази при зареждане
Откъде идва името на поддиректорията
На Windows то е Win32 или Win64, решавано от битовата ширина на изпълняващия се процес, а не на операционната система, защото това е това, което определя кой двоичен файл може да бъде зареден; Навсякъде другаде името е изградено от compiler target макросите, така че машина, компилираща за две архитектури, произвежда две ясно отделени дървета и така че папката, държаща нативната библиотека, седи до папката, държаща компилираните модули, със същото име
function BuildDllSubDir(UseV8: Boolean): string;
begin
{$IFDEF MSWINDOWS}
if IsWin64 then
Result := 'Win64'
else
Result := 'Win32';
{$ELSE}
// Компилаторните макроси пишат OS с главна буква („Linux“, „Darwin“),
// докато изходната директория на package модула не, така че двете
// съвпадат само след folding. На чувствителна към регистъра файлова
// система тази разлика е цялото търсене
Result := LowerCase({$I %FPCTARGETCPU%} + '-' + {$I %FPCTARGETOS%});
{$ENDIF}
end;
Защо една главна буква счупи цялата верига
Компилаторното макро изписва целевата операционна система с начална главна буква: Win64, Linux, Darwin; Lazarus пакетът записва изхода на своите модули в директория, кръстена от собствена му target променлива, която е с малки букви: win64, linux, darwin; Две изписвания на едно и също и никакъв начин да се забележи на Windows, където файловата система не ги разграничава
На Linux те са две различни директории; Deployment, който слага shared object-а в DLLs/x86_64-linux, е невидим за loader, търсещ DLLs/x86_64-Linux, така че и четирите изрични стъпки на веригата пропускат и кодът пада през към оставянето на platform loader-а да търси; Понякога това работи, ако библиотеката случайно е инсталирана system-wide, и понякога не и и в двата случая внимателно подреденото deployment дърво не допринася нищо; Отказът няма съобщение за грешка, защото нищо не се е провалило: всяка стъпка правилно докладва, че файлът не е там, където тя гледаше
Пробната програма, компилирана и изпълнена
Този клас дефекти не може да бъде намерен с четене и не може да бъде намерен и с компилиране; Обичайната техника за верифициране на platform клон, който никога не се компилира на машината за разработка, е да копирате модула във временна директория, да го преименувате, да замените platform условния израз със символ, който никога не е дефиниран, и да компилирате копието; ако се компилира, uses клаузата и call сигнатурите на този път са поне самосъгласувани; Това работи добре за самостоятелен модул
Тук не работи; Основният binding модул е много голям и дърпа LCL, така че не може просто да бъде копиран и компилиран с Windows символа изключен; Така че вместо това шепата функции, които промяната докосна, бяха преписани дословно в малка самостоятелна програма и тази програма беше пусната; Тя отпечата x86_64-Win64 и разминаването беше видимо в един ред изход; Компилирането на същата програма не би ви казало нищо, защото низът е напълно валиден; само стойността му е грешна
program ProbeSubDir;
{$MODE DELPHI}
uses
SysUtils;
begin
// Печатайте, не assert-вайте. Точката е да погледнете стойността,
// до която макро действително се разширява на тази toolchain
Writeln('raw: ', {$I %FPCTARGETCPU%}, '-', {$I %FPCTARGETOS%});
Writeln('folded: ', LowerCase({$I %FPCTARGETCPU%} + '-' +
{$I %FPCTARGETOS%}));
end.
Общият урок: когато кръстосано-платформена промяна засяга стойността на нещо, а не неговия тип, само-компилаторна верификация не е верификация; Принтирайте го; По-широкият набор от кръстосано-компилаторни разлики между Delphi и Free Pascal е събран в статията за Delphi и FPC кръстосано-компилаторни капани
Оставете платформата да обясни собствените си откази при зареждане
Windows клонът на loader-а изброява на ръка причините, поради които зареждане може да се провали, защото полезните разграничения там — несъвпадение на архитектура, липсваща транзитивна зависимост, път, който не резолвира — съпоставят на error кодове, които си заслужават да бъдат назовани поотделно; Извън Windows портабилният loader модул вече връща описателен низ, който покрива същата територия, така че не-Windows клонът го използва директно, вместо да преизвежда категории от error число, което значи различни неща на различни системи
Съпротивата срещу порива да се нормализират тези две в едно съобщение е нарочна; Отказ при зареждане е deployment проблем и човекът, четящ съобщението, се нуждае от собствения речник на платформата, за да го потърси
Сблъсък на имена, който се рекурсира
Още един капан, малък и остър; Портабилният loader модул експортира процедура, казваща се UnloadLibrary, а binding модулът има процедура със същото име, която прави собственото си счетоводство, преди да пусне handle-а; Вътре в тази процедура неквалифицирано извикване на UnloadLibrary резолвира към онази в текущия модул, която вика себе си; Поправката е да се квалифицира извикването с името на модула
Това е същата форма като проблемите със засенчване на идентификатори, които доминират Free Pascal преносите изобщо: Windows модулът експортира целочислени типизирани функции за минимум и максимум, които засенчват floating-point такива и тип за синхронизация, който засенчва класа със същото име и във всеки случай резолюцията зависи от реда на uses клаузата; Квалифицирането на мястото на извикване е поправката, която не зависи от някой да запази този ред по-късно
Deployment контролен списък
Три неща обясняват повечето откази при зареждане, щом пътната аритметика е права; Архитектурата трябва да съвпада с процеса, не с машината, така че 32-битово приложение на 64-битов Windows се нуждае от 32-битовия двоичен файл; V8-включената компилация има различно име на файл, така че deployment, който ги смесва, ще изглежда правилен и няма да зареди нищо; И само един вариант може да живее в системна директория в даден момент, което е добра причина да се предпочете изричното поддиректорийно оформление пред инсталирането на каквото и да е system-wide
За Lazarus конкретно, сложете нативната библиотека под DLLs/<cpu>-<os> с малки букви, до изпълнимия файл и тя ще бъде намерена от първата стъпка на веригата на всяка цел; Примерът viewer, който упражнява това на Lazarus, е описан в статията за Lazarus и FPC viewer, а текущата поддръжка на платформи е изброена на продуктовата страница PDFium Delphi component