يبني HotXLS تحت Free Pascal وLazarus على Windows، وقد دار النقل حول أربعة قرارات لا علاقة لها بقواعد Object Pascal: إبقاء النواة في وضع DELPHIUNICODE، وإعلان واجهات التخزين المهيكل OLE بوصفها واجهات CORBA بعدّ مراجع يدوي، واستبدال ملفات كائنات AES لـ Win32 بتنفيذ Pascal، وإصلاح حلقة inflate كانت تستطيع قبول ZIP مقطوع بوصفه تامًا
كل من نقل مكتبة Delphi ناضجة يعرف شكل هذا العمل. يقبل المترجم كل شيء تقريبًا في التمريرة الأولى. وما يليها ذيل طويل من الفروق السلوكية التي تُصرَّف بنظافة وتنتج نتائج خاطئة، ومحرك جداول معرَّض لها تعرّضًا غير معتاد لأنه يلمس ترميز النص، والتخزين المهيكل COM، والضغط، والتشفير في مسار كود واحد
لماذا تُصر النواة على DELPHIUNICODE لا DELPHI المجردة
لأن محرك الصيغ يعتمد على String وChar حاملَي دلالات UTF-16، والبديل ANSI يضيّع المحارف قبل أن يصل أي شيء إلى الملف. من المغري أن تبني النواة في وضع FPC DELPHI، فهو مفتاح التوافق الذي تلجأ إليه معظم عمليات النقل، والكود يُصرَّف. ثم يدور مصنف بأسماء أوراق صينية أو ملصقات سيريلية عبر مسار الحساب وتختفي المحارف قبل أن يراها الكاتب، بلا أي خطأ في أي مكان
والوضع ليس موحدًا عبر المكتبة، وهذا متعمد لا فوضى. فمفكك بايتات PNG والتجاوزات الخاصة بـ LCL تحتاج فعلًا توقيعات ANSI، لأنها تتعامل مع البايتات ومع ما تسلمه مجموعة الودجات. وتفعّل تلك الوحدات مفتاحًا منفصلًا هو LX_FPC_ANSI. وضعان في مكتبة واحدة يبدوان رائحة سيئة حتى تنتبه أن البديل مفكك بايتات يعامل مدخله معاملة نص
وهناك تفصيلة رفيقة تصطاد الناس لاحقًا. فـ DELPHIUNICODE لا تجعل TFormatSettings.DecimalSeparator بـ WideChar في وقت تشغيل FPC. فالمدخل الحامل فاصلًا عشريًا يونيكوديًا عليه أن يُطبَّع إلى فاصل ASCII داخل سلسلة اليونيكود أولًا، وأي مدخل لا يطابق فاصله المتوقعًا يجب رفضه لا اقتطاعه بصمت عند المحرف الذي لم يتعرف عليه المحلل
program ExportReport;
{$MODE DELPHI}
uses
Interfaces, // يجب أن تأتي أولًا: تهيئ مجموعة ودجات LCL
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.
ووحدة Interfaces ليست اختيارية وعليها أن تأتي أولًا. فهي ما يهيئ مجموعة ودجات LCL وطبقة تحويل UTF-8، ويعتمد HotXLS على الاثنين ما إن تعبر الخطوط أو مسارات الملفات أو النصوص حدود RTL وLCL. وبرنامج كونسول يتخطاها سيُصرَّف وسيسلك سلوكًا سيئًا على أي مسار غير ASCII. وهذا أيضًا سبب أن يثبت التصريف الناجح القليل هنا: لم يكن النقل يعمل ظاهرًا إلا حين أدارت وثائق حقيقية بأسماء خطوط حقيقية ومسارات حقيقية دورة كاملة
VMT صنف ليست vtable لـ COM
لن تدعك Free Pascal تسلّم VMT صنف إلى Windows بوصفها vtable واجهة COM، حتى لو بدا الإعلان مطابقًا لما تقبله Delphi. فالتخطيطان يختلفان بطريقة تنتج استدعاءً إلى خانة خاطئة، يتجلى كانهيار في مكان لا علاقة له بموقع الاستدعاء. والتخزين المهيكل مهم هنا لأن صيغة المصنف الثنائية الكلاسيكية ملف مركب OLE، وقراءة واحد أو كتابته تعني تنفيذ ILockBytes ستعاود واجهة تخزين Windows استدعاءها
والترتيب العامل واجهة CORBA بخانات COM معلنة صراحة وAddRef وRelease مُدارتَين يدويًا. وهذا يعني التخلي عن عدّ المراجع التلقائي لهذه الأنواع وتحمل مسؤولية العمر، وهي مقايضة عادلة لقليل من الواجهات تسكن وحدة واحدة. والفخ المحدد داخل ذلك العمل هو QueryInterface: عليها أن تعيد مؤشر واجهة لا مؤشر الكائن. وكلاهما يُصرَّف. وواحد منهما يسلّم Windows عنوانًا تكون كلمته الآلية الأولى ليست vtable
وتسكن الإعلانات الخاصة بـ FPC في lxOleInterfaces.inc، بجوار lxAESBackend.inc وlxZlibBackend.inc في مجلد مصادر FPC، فتجلس القرارات الخاصة بالمترجم في مكان واحد بدل أن تنثر عبر المحرك. والصيغة نفسها وكيفية تنقل المكتبة فيها موصوفان في قراءة الملفات المركبة OLE2 بـ Pascal
وتفصيلة أنواع أخرى تنتمي إلى العائلة نفسها. يجب أن يحل LargeInt إلى Int64 في فرع FPC، وتصنيف المترجم لـ Comp يختلف بين السلسلتين بما يكفي ليستطيع حل التحميل الزائد اختيار مرشح مختلف. اختبر سلوك الإزاحات الكبيرة بتدفق ملف لا بتدفق HGLOBAL: فتدفق الذاكرة الشاملة في Windows يلتف على عمليات seek بعد 4 GiB بنفسه، فاختبار ناجح هناك لا يثبت شيئًا عن حسابك أنت
ما يخفيه تنفيذ AES متسق مع نفسه
ملفات كائنات AES لـ Win32 التي يربطها بناء Delphi بصيغة OMF، ولا يستطيع رابط Free Pascal استهلاكها، ففرع FPC يستخدم تنفيذ AES بـ Pascal بدلًا. ويواصل Delphi ربط ملفات الكائنات التي ربطها دائمًا، فيبقى الثنائي المطلق دون تغيير للعملاء الحاليين
ومطلب التحقق هو الجزء الذي يستحق حمله إلى أي مشروع. فتشفير بيانات ثم فك تشفيرها بالتنفيذ نفسه لا يثبت شيئًا إطلاقًا: خوارزمية متماثلة بجدول مفاتيح خاطئ أو ترتيب كتل خاطئ أو تسلسل خاطئ متناسقة مع نفسها تمامًا وستدور على مخرجها كل مرة. ومتجهات الإجابة المعروفة وحدها تكشفها، فتتحقق من توسيع المفتاح وترتيب الكتل وتسلسل CBC مقابل قيم منشورة. اشحن تنفيذًا خاطئًا متناسقًا مع نفسه وسيظهر العَرَض أول مرة يفتح فيها عميل الملف في Excel
أما الضغط فعيب من طابع مختلف. فخلفية inflate بـ Pascal قد يبقى عندها مخرج معلق بعد أن استهلك كل مدخله المضغوط، فعلى المستدعي أن يواصل الاستدعاء حتى يبلّغ التدفق عن نهايته. ومعاملة المدخل المستنفد بوصفه نهاية تدفق تقطع الكتلة الأخيرة. وأسوأ من ذلك أنها تحول أرشيفًا تالفًا إلى مقبول بصمت، وهو تحديدًا نمط الفشل الذي جاءت تقوية التحقق من سجل ZIP نهاية الدليل المركزي لمنعه. والقاعدة: لا تقدم مع عدم الانتهاء هو خطأ اقتطاع، وليس EOF قط
فخّا نظام بناء يكلفان ساعات حقيقية
على مسارات بحث LCL أن تسبق مسارات البدل لحزم FPC، وإلا ظلّلت وحدة Menus لـ Free Vision وحدة LCL بالاسم نفسه وحصلت على عدم تطابق checksum لـ PPU لا يقول شيئًا عن أي منهما. ومنصبة Lazarus نُقلت بعد التنصيب قد تترك أيضًا مسارات بالية في fpc.cfg، لذا تحدد نقاط دخول البناء مسارات الوحدات والثنائيات صراحة بدل أن ترث ما يقدمه المحيط
والفخ الثاني لا علاقة له بـ Pascal. فملف دفعي .cmd مكتوب بنهايات أسطر LF يعمل حتى يكبر الملف فوق حجم ذاكرة قراءة المفسر، وعندها يفشل call :label بدعوى أن التسمية الدفعية غير موجودة، ويظهر الإخفاق عند أي برنامج صادف أنه يجلس بعد الحد. وكل أداة تعيد كتابة سكربت دفعي عليه أن يكتب CRLF ردًا. وlazbuild --build-all يمسح مجلد مخرجات وحدات الحزمة قبل التصريف، فملف خيارات مرسو في ذلك المجلد يُحذف قبل أن يُقرأ: أبقه خارجه، وتذكر أن مسار @ يُحل نسبةً إلى مجلد الحزمة لأن lazbuild يستدعي المترجم من هناك
// تصدير شبكة Lazarus: TGridToXLS ترد في حزمة Lazarus، فيعمل كود
// تصدير شبكة DB نفسه في تطبيق 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 حوّل ذلك الفرق إلى عيبين حقيقيين في وحدة الحساب. فدالة قرأت متغير عد لم يُسند قبل الاستخدام قط، وأخرى استخدمت إحداثيتين في فرع واحد قبل أن يجري في فرع مختلف الكود الذي حسبهما. وتحت Delphi تصرفت الاثنتان وفق ما صادف أن المكدس حمله، وهذا تعريف خطأ يستنسخ على آلة ولا يستنسخ على أخرى
والنتيجة العملية أن المترجم الثاني يستحق البقاء في الحلقة حتى لمنتج يشحن أساسًا على الأول. فمسح فئات تحذيرات FPC دوريًا تمريرة تحليل ساكن رخيصة على قاعدة كود Delphi، ويجد صنفًا من العيوب لا تبلغه مجموعة اختبارات بموثوقية. والانضباط الأوسع لمصفوفة الإصدارات الذي يجلس داخله هذا موصوف في مصفوفة البناء عبر المترجمات
ودعم Free Pascal وLazarus لـ Windows يرد مع مكوّن جداول HotXLS Delphi بوصفه حزمة Lazarus إلى جانب حزمتي Delphi وC++Builder، مبنيًا من شجرة المصدر نفسها لا من تفرع. وهذا هو مقصود التمرين: محرك واحد، وأربع سلاسل أدوات، والقرارات الخاصة بالمترجم معزولة في ملفات include يمكن قراءتها في جلسة واحدة