يبني PDFlibPas تحت Free Pascal لأنظمة Windows ذات 32-بت، والجزء الصعب لم يكن يومًا Pascal نفسها، بل ملفات الكائنات: كائنات AES وOpenJPEG التي يربطها بناء Delphi بصيغة OMF، بينما يشترط الرابط الداخلي لـ Free Pascal صيغة COFF، والتحويل بين الصيغتين ينتج أسماء مقاطع ورموز تعريف مقاطع تجعل الرابط يفشل بأخطاء داخلية بدل رسائل تشخيصية
كل من ربط كائنات C في مكتبة Pascal يعرف هذه المنطقة جيدًا. فـ Win64 مهذّب نسبيًا: صيغة كائنات واحدة، واصطلاح استدعاء واحد، ولا زخرفة أسماء. أما Win32 فيحفظ كل طبقة من تاريخ المنصة المتراكم، والمكتبة التي تربط كود C لطرف ثالث ارتباطًا ساكنًا تصطدم بها كلها دفعة واحدة
مجلد المترجم لا يخبرك بالهدف
ابدأ من نقطة دخول البناء، فالخطأ هنا يضيّع ساعات قبل أن يُذكر أي ملف كائنات. فاسم مجلد تنصيب Free Pascal يحدد مكان المترجم الرئيسي، لا ما يُنتجه. فالمترجم المضيف ذو 32-بت يستطيع استدعاء cross-compiler يجاوره وإخراج كود 64-بت إذا مرّرت مفاتيح الهدف الصحيحة، لذا فاستنتاج الهدف من المسار تخمين يصادف نجاحه حتى يعيد أحدهم ترتيب سلسلة الأدوات
والطريقة الموثوقة هي أن تسأل المترجم نفسه. استعلم عن معالج الهدف ونظام التشغيل الفعليين عبر مفاتيح المعلومات التي يوفرها المترجم، وتقبل تخطيطَي التنصيب الشائعين معًا: مجلد binaries المسطّح والمتداخل حسب الإصدار، لأن المنصّبات ومديري سلاسل الأدوات المختلفة ينتجون أشكالًا مختلفة. فسكربت بناء يثبّت أحد التخطيطين كتابعةً يعمل على جهاز واحد بالضبط
لماذا يكسر ملف الكائنات المحوَّل الرابط الداخلي
لأن التحويل يحافظ على اصطلاح تسمية مقاطع OMF ويصنع رموز تعريف مقاطع لا تطابق ما يتوقعه رابط COFF. فتحويل كائنات OMF إلى COFF ضروري لكنه غير كافٍ: الملفات الناتجة تحمل أسماء المقاطع الكلاسيكية _TEXT و_DATA و_BSS، إضافة إلى أسماء رموز تعريف المقاطع المشتقة منها، وتغذية ذلك إلى الرابط الداخلي لـ Free Pascal تنتج أخطاء مترجم داخلية بدل رسالة عن تسمية المقاطع
والخطأ الداخلي أسوأ أنماط الفشل لمشكلة بناء، لأنه لا يقول شيئًا عن موضع الخلل في المُدخل. والحل تمريرة تطبيع بعد التحويل على ملف COFF: إعادة كتابة أسماء المقاطع إلى الصيغة المتوقعة، وإعادة كتابة رموز تعريف المقاطع المقابلة لتطابقها، مع ترك فهرس الرموز وبايتات الكود وإعادة التوطين بلا مساس. وهذا القيد الأخير هو كل الصعوبة؛ فإعادة كتابة تعيد ترقيم الرموز أو تزيح الإزاحات تنتج كائنًا يُربط ثم ينهار
وهناك خطوة تمهيدية خاصة بإحدى مجموعتي الكائنات. فكائنات OpenJPEG المبنية بمترجم C++ الكلاسيكي ذي 32-بت تعتمد على روتينات مساعدة خاصة بـ Delphi لعمليات الأعداد الصحيحة 64-بت، وFree Pascal لا يوفرها، فلا تحويل صيغ مهما بلغ يجعلها صالحة للاستخدام. تُبنى تلك من جديد أولًا بالمترجم المعتمد على Clang الذي لا يُخرج تلك الاعتماديات، ثم تُحوَّل بعدها
// كائنات هدف FPC تسكن في مجلد خاص بها، وهي لا تحل محل
// مجموعة كائنات Delphi، لأن السلسلتين تبنيان من شجرة المصدر
// نفسها وكل واحدة تحتاج مدخلات ربط خاصة بها
//
// Lib\thirdparty\Win32 كائنات OMF الخاصة بـ Delphi، دون تغيير
// Lib\thirdparty\Win32f كائنات COFF الخاصة بـ FPC، محولة ومطبَّعة
//
// نقاط دخول البناء:
// build-Win32-Lib-FPC.cmd
// build-Win64-Lib-FPC.cmd
المساعدات الخاصة بالمترجم غير قابلة للنقل، واصطلاحاتها كذلك
يوفر وقت تشغيل Delphi مسارات انتقال (trampolines) بلغة التجميع لعمليات الأعداد الصحيحة 64-بت على معمارية x86 ذات 32-بت، وكائنات C المصرَّفة مسبقًا لـ Delphi تستدعيها. ولـ Free Pascal ترتيبه الخاص، فلا بد من إشباع تلك المراجع بطريقة مختلفة لا بإعادة توجيهها. والتفصيلة التي تجعل إعادة التوجيه مستحيلة هي اصطلاح الاستدعاء: المستدعى هو من ينظف وسيط المساعد الخاص بالتوقيت الذي يستخدمه كود التصوير ذا الأربع بايتات، بينما يمسح مساعد القسمة 64-بت ست عشرة بايتة ويعيد نتيجته في زوج السجلات الكلاسيكي. مساعدان واصطلاحان، وtrampoline مكتوبة لأحدهما تفسد المكدس بصمت للآخر
وزخرفة الأسماء تضيف النصف الثاني من المشكلة. فعلى Win32 تسبق Free Pascal واردات C الخارجية بخط سفلي تلقائيًا، بينما تصدّر التعريفات public name حرفيًا، فيتبع جانبا الجسر نفسه قاعدتين مختلفتين. لذا فجسر وقت تشغيل C الذي تحتاجه OpenJPEG لا بد أن يصدّر أسماء رموز C بالضبط، وأن تستخدم نقاط الدخول المتغيرة الوسائط قفزةً غير مباشرة بـ 32-بت بدل المباشرة. ولا شيء من هذا غريب ما إن يُقال، لكنه كله يفشل على هيئة خطأ ربط يذكر رمزًا لم يكتبه أحد
ما الذي أودى بتنفيذي Win32 قبل main
ملف DLL ذو 64-بت كان على مسار البحث، ووصلنا إليه لأن وحدة zlib في Free Pascal ترتبط ديناميكيًا بدل الربط الساكن. وكان العرض خروجًا فوريًا برمز الحالة invalid-image، قبل أن يعمل أي كود Pascal في البرنامج، وهذا يرسلك تفتش في البرنامج الذي بنيته للتو بينما الخلل في المحمِّل وهو يحل إحدى الوردات على معمارية خاطئة
والعبرة هنا بالافتراضات لا بـ zlib. فالوحدة التي تحمل اسم مكتبة ضغط لا تحتويها بالضرورة؛ فقد تكون مجرد ربط ينتظر مكتبة مشتركة وقت التشغيل، والاعتمادية الديناميكية التي لم تقصدها عبء نشر حتى لو صادف أنها تُحل. والتحول إلى تنفيذ التدفقات البحت بـ Pascal يمنح الهدفين مسار ضغط يُضمَّن ساكنًا دون أي اعتمادية خارجية، وهذا ما يجب أن تمتلكه مكتبة ستُدمج في تطبيق شخص آخر من الأساس
وينطبق الحدس نفسه على الواجهة الخلفية لمرمِّز JBIG2 الخارجي. فعلى الهدف ذي 32-بت لا يُربط المرمِّز الخارجي، فترجع الطلبات إلى المرمِّز المدمج بـ Pascal، والاختبار الذي يتحقق من ذلك عليه أن يفحص حالة التسجيل للهدف الحالي بدل أن يعتبر ترميزًا ناجحًا دليلًا على وجود الواجهة الخارجية. فالبديل الاحتياطي الذي يعمل هو تحديدًا ما يخفي اعتمادية مفقودة، وهو نمط الفشل الذي ناقشته مقالة تشخيص أعطال الـ stubs الصامتة. أما عمل الربط الساكن ذي 64-بت فمغطى في الربط الساكن لـ jbig2enc تحت FPC
حساب 32-بت على تدفق ذاكرة
الكود الذي يعالج أحجام الوسائط بحساب بدون إشارة بعرض المؤشر يكون صحيحًا على Win64، وبعد خطوة واحدة من الفيضان على Win32 مع صورة كبيرة. فتدفق الذاكرة الذي يغذي برنامج ترميز JPEG 2000 ينمو بالمضاعفة ويتقدم بالجمع، وعلى هدف 32-بت يمكن للعمليتين أن تلتفّا مع مدخلات كبيرة لكنها مشروعة تمامًا
لذا فكل كتابة وتخطٍّ وseek وتخصيص أولي تتحقق قبل أن تحسب، وسقف السعة هو أكبر قيمة بإشارة بعرض المؤشر، مختارة لتطابق ما يستطيع روتين نقل الكتل وقيم إرجاع الـ callback التعبير عنه. والمطلب السلوكي عند رفض طلب سهل الخطأ فيه: الرفض يجب ألا يغير موضع التدفق ولا طوله. فتعديل جزئي يليه خطأ يترك التدفق في حالة لا يستطيع المستدعي التفكير فيها، والعملية التالية تضاعف المشكلة
// تحقق قبل أن تحسب. على Win32 تلتف كلتا العمليتين مع مدخلات
// تنتجها صورة JPEG 2000 كبيرة بشكل مشروع
if Needed > NativeUInt(High(NativeInt)) - FPosition then
Exit(False); // ارفض، واترك الموضع والحجم كما هما
NewCapacity := FCapacity;
while (NewCapacity < FPosition + Needed) do
begin
if NewCapacity > NativeUInt(High(NativeInt)) shr 1 then
Exit(False); // المضاعفة ستفاض
NewCapacity := NewCapacity shl 1;
end;
فخّا مخرجات بناء يبقيان بعد انتهاء النقل
فصل تنفيذيات الاختبار والعينات حسب معمارية الهدف في مجلدات مخرجات لكل هدف أمر صائب بديهيًا، ويكسر فورًا كل شيء كان يحدد موقع بيانات اختباره بعدّ مستويات المجلدات صعودًا. والحل أن يبحث صعودًا عن مجلد الأصول بدل افتراض عمق ثابت، مع قيد متعمد واحد: عينة التوقيع لا تقبل شهادة بديلة إلا من مجلد مشروعها، ولا من أي سلف اعتباطي، فشهادة تحمل الاسم نفسه تُعثر عليها أعلى في الشجرة مفاجأة أمنية لا تسهيل
والفخ الثاني ينجو من كل نقل ويستحق أن يُحمل إلى أي مشروع FPC. فبعد ترقية المترجم لا يكفي أن يرفض المترجم ملفات PPU البالية، لأن الرابط ما يزال يفضّل ملفات الكائنات المتبقية في مسار بحث الوحدات حتى لو جاء ملف PPU الذي حمّله من المجلد الصحيح، وإضافة مسار مخرجات كائنات صريح لا تلغي ذلك التفضيل. والجواب الموثوق الوحيد مجلد وحدات مؤقت جديد في كل جولة بناء. وأقل من ذلك ينتج ثنائيًا مرتبطًا من إصداري مترجم، يفشل بطرق تبدو كأخطاء في المصدر
والتعليمات الشرطية الخاصة بالمنصة هي القطعة الأخيرة، واختيار المحور الصحيح أهم مما يبدو. فالسؤال الصائب عادةً هو هل الكود خاص بـ Windows، لا هل توجد مكتبة widgets معينة، كما بيّن عمل تحويل الميتافايل في استيراد EMF المتجهي والتعليمات الشرطية للمنصات: تحويل ذلك الحارس من شرط مكتبة تحكم إلى شرط منصة حوّل إعادة كتابة مفترضة إلى تعديل توجيه واحد. ودعم Free Pascal وLazarus لكلا هدفَي Windows يأتي مع مكتبة PDFlibPas Delphi PDF، المبنية من المصادر نفسها لحزم Delphi وC++Builder