Free Pascal на Win32 добавя водеща подчертавка към всеки cdecl; external импорт автоматично, докато public name експортира низа, който сте написали, знак по знак. HotPDF трябва да удовлетвори и двете конвенции в едно и също source дърво, защото Delphi build-ът вече ship-ва импорт декларации, изписващи подчертавката на ръка. Разминаването с тази асиметрия ражда link грешки, назоваващи символ, който никой не е писал
Разширяването на Delphi библиотека към Free Pascal обикновено се описва като проблем на преносимостта, и на Win64 основно е така. Win32 е друг. 32-битовият x86 Windows ABI носи трийсет години натрупана конвенция за това как се изписват C символите, кой чисти стека и кои компилаторно-частни helper-и един translation unit има право да допусне, и всяко от тях е място, където два Pascal компилатора, съгласни по езика, могат пак да не са съгласни по обектния файл
Защо същият символ се разрешава на Win64 и се проваля на Win32?
Защото подчертавката като префикс е 32-битова конвенция, която Free Pascal прилага към импортите, но не и към експортите. Обявете function deflate(...): Integer; cdecl; external; и FPC търси _deflate в обектния файл на Win32, и deflate на Win64. Това е коректно поведение и съвпада с това, което C компилатор излъчва. Капанът е от другата страна на моста: рутина, маркирана public name 'deflate', експортира точно deflate и на двете цели, без добавен префикс
Сега добавете историческия детайл, който го прави конкретно. Delphi build-ът вече обявява някои от тези входни точки с подчертавката изписана в името, защото това съдържат собствените му обектни файлове. Подайте същата декларация на FPC на Win32 и компилаторът изпълнително я префиксира отново, така че linker-ът лови __deflate – символ, който нищо не експортира. Интуитивната поправка, една подчертавка навсякъде, чупи импортите, които вече са изписани правилно
Работещото е двойка префиксни константи, а не една. HPDFFPCZLib и HPDFFPCCodecStubs ползват един префикс за обикновени C импорти и друг за импорти, които вече носят Delphi префикс, а на Win64 и двете константи са празни, така че съществуващите link имена оцеляват недокоснати. Две константи вместо една е цялата поправка, и тя е очевидна само щом сте отделили импортното правило от експортното
// Две префиксни константи, не една: обикновените C импорти и импортите,
// вече носещи ръчно изписана Delphi подчертавка, се декорират различно под FPC/Win32
const
{$IF DEFINED(FPC) and DEFINED(CPU32)}
CPrefix = '_'; // FPC добавя това сам за cdecl external
DelphiCName = ''; // вече изписано с подчертавката в source-а
{$ELSE}
CPrefix = '';
DelphiCName = '';
{$IFEND}
// Експортна страна: 'public name' е дословна на всяка цел
procedure hpdf_codec_free(P: Pointer); cdecl;
public name 'hpdf_codec_free';
WIN32 ви казва архитектурата, не ABI-то
Това е грешката в условната компилация с най-дългата дебъг опашка, и си струва да се изрече ясно: WIN32 и WIN64 описват целевата архитектура и не казват нищо за това кои компилаторно-частни runtime helper-и съществуват. Free Pascal дефинира и двата символа на съответните Windows цели, точно като Delphi. Guard, написан {$IFDEF WIN32} около код, който вика Delphi runtime helper, затова се компилира под FPC и се проваля на link
Конкретно три семейства код падат в този капан. Delphi 64-битовите integer trampolines, достигани през System.@_ll helper-ите, MSVC Win32 асемблерните support рутини и импорт слотовете, идващи с тях, съществуват, за да обслужват предварително компилирани C обекти, които Delphi build-ът свързва. Free Pascal не свързва тези обекти, така че не му трябва нито капка от тази машина, и всяка референция към нея трябва да изчезне. Фината точка е, че декларацията и имплементацията трябва да бъдат изключени заедно. Изключите ли само едната, компилаторът докладва нещо безполезно за идентификатор, който не може да съпостави с нищо
Правилото, което изпада оттук, е кратко. Guard-вайте по компилатора, когато въпросът е за ABI или runtime поддръжка, guard-вайте по архитектурата, когато въпросът е за широчина на указателя или брой регистри, и никога не позволявайте едното да замества другото
Guard-ване на декларации и имплементации заедно
Условен блок в interface секцията е лесно да влезете, без да забележите, а полученото съобщение за грешка сочи навсякъде, но не и към причината. Добавите ли декларация на метод към class interface, естественото място е до сродните методи, което е добре точно до момента, в който тези съседи се окажат в съществуващ {$IFDEF} блок. Условните директиви не се отместват с интервали, така че блок, отворен четиридесет реда по-горе, е практически невидим, докато четете заобикалящите декларации
Следващото е компилация, минаваща на един toolchain и раждаща каскада на друг. Ако заобикалящият guard е проверка за версия на Delphi, която Free Pascal не удовлетворява, декларацията изчезва за FPC, докато безусловната имплементация остава, и компилаторът докладва дълъг списък оплаквания за method идентификатори, които е очаквал и не е намерил. Нито едно от съобщенията не споменава условния блок, причинил ги
Два навика спират целия клас провали. Преди да вмъкнете в interface секция, гледайте нагоре за най-близкия отворен conditional, вместо да вярвате на визуалното групиране. И третирайте зелена Delphi test suite като доказателство само за Delphi: Free Pascal library build-ът е отделна порта и единственият начин да знаете, че минава, е да изпълните build-Win32-Lib-FPC.cmd и build-Win64-Lib-FPC.cmd като част от същата промяна
Какво чупи 32-битов аритметичен код
Едно езиково ограничение се появява именно в кода, най-малко склонен да се мени: 32-битов Free Pascal не приема UInt64 като контролна променлива на for цикъл. В elliptic-curve unit-ите, носещи X25519 и X448, циклите, обикалящи limb масивите, са писани с 64-битови броячи просто защото всичко останало във файла е 64-битово
Поправката трябва да е хирургична, защото в полевата аритметика широчината на променлива е част от аргумента за коректност. Loop индексите стават Integer, тъй като limb масив има шепа елементи и никакъв индекс не се доближава до 32-битовия диапазон. Всичко, участващо в аритметиката – самите limbs, преносът (carry) и маските – остава UInt64, защото стесняването на което и да е от тях безшумно мени резултата модуло полевия прост
// 32-битов FPC отхвърля UInt64 loop променлива. Стеснете само индекса;
// limbs, маски и carry-та пазят широчината си или полевата математика се мени
var
I: Integer; // беше UInt64
Carry, Mask: UInt64;
begin
Carry := 0;
for I := 0 to High(Limbs) do
begin
Limbs[I] := Limbs[I] + Carry;
Carry := Limbs[I] shr 51;
Limbs[I] := Limbs[I] and Mask;
end;
end;
Верификацията за такава промяна не може да е round-trip тест. Кодиране и декодиране с една и съща счупена имплементация си съвпадат перфектно със самите себе си, което е причината known-answer векторите да са непререкаеми тук: изпълнете публикуваните X25519 и X448 тестови вектори и сравнете точните изходни байтове. Това е единствената проверка, различаваща коректна имплементация от самосъгласувана грешна, и важи също толкова за симетричните примитиви, обсъдени в Free Pascal deflate и AES codec границите
Какво струва Win32 Free Pascal build
Практическата изгода е, че Lazarus приложение, целящо 32-битов Windows, получава същия документен двигател като Delphi си двойник, без отделен binary договор за поддръжка. Това е най-важно за deployment-ите, за които хората рядко говорят: индустриални контролери, POS терминали и дълговечен line-of-business софтуер, където 32-битовият runtime не е легаси избор, а хардуерно ограничение
Win64 историята дойде първа и е описана в Free Pascal и Lazarus поддръжка на Win64. Win32 не е нейно повторение. Win64 има една calling convention, никакво name decoration и никакви Delphi-частни integer helper-и за заобикаляне, така че почти всичко в тази статия е специфично за 32-битовата цел. Аритметичните unit-и, нуждаещи се от смяната на loop променливата, са същите, описани в Montgomery аритметика върху NIST кривите, където ширинната дисциплина е обяснена по-надълбо
Общият урок е, че работата по cross-компилаторна преносимост не е преди всичко за езикови възможности. И двата компилатора приемат един и същ Object Pascal тук. Различното е обектният файл: как се изписват символите, кои helper рутини се допуска, че runtime-ът доставя, и кои предварително компилирани обекти са в link-а. HotPDF ship-ва Free Pascal и Lazarus пакетите редом с Delphi и C++Builder в HotPDF Delphi PDF компонента, така че едно source дърво храни всеки toolchain, вместо разклонение по компилатор