רכיב PDFium מאתר את ספריית ה-native שלו דרך שרשרת חיפוש קבועה ומסודרת במקום להשאיר זאת לטוען של מערכת ההפעלה, כי עץ פריסה מפורש הוא עץ פריסה שאפשר לנפות בו באגים. ב-Windows השרשרת מחפשת תת-ספריית Win32 או Win64 שהמתקין כבר משלח. ביעדים אחרים היא בונה את שם תת-הספרייה מפקודות המאקרו של היעד של Free Pascal, כ-<cpu>-<os>, כך שעץ הפריסה נקרא בדיוק כעץ היחידות המקומפלות. ההחלטה האחרונה הכניסה באג ששווה את המאמר כולו, כי הגורם היה אות גדולה והתסמין היה שתיקה
השרשרת, לפי הסדר
ארבעה מיקומים, נבדקים ברצף, ואז הטוען של הפלטפורמה כמוצא אחרון. ראשית הפריסה המועדפת, ספריית DLLs ליד קובץ ההרצה שמכילה תת-ספרייה אחת לכל יעד. שנית פריסה חלופית עם תת-ספריית היעד ישירות ליד קובץ ההרצה. שלישית הפריסה השטוחה הישנה, הספרייה יושבת ליד קובץ ההרצה בלי תת-ספרייה בכלל. רביעית, ב-Windows בלבד, ספריית המערכת, שדורשת זהירות כי תהליך בן 32 ביט חייב להביט ב-SysWOW64 ותהליך בן 64 ביט ב-System32, וב-Windows בן 32 ביט הראשונה לא קיימת ולכן החיפוש חייב ליפול אחורה. רק אחרי כל זה מבקשים מהטוען לחפש בעצמו
אין בכוונה שלב של ספריית מערכת מחוץ ל-Windows. נתיב החיפוש של הטוען של הפלטפורמה עצמו, שמונע על ידי תצורת מקשר ה-runtime וסביבת נתיבי הספריות, כבר מכסה את השטח הזה, ושכפולו בפסקל פירושו לממש מחדש כללים שמשתנים לפי הפצה. אבחון כשלים בשרשרת של Windows מכוסה בנפרד בפריסת ה-DLL של PDFium ואבחון כשלי טעינה
מאיפה שם תת-הספרייה מגיע
ב-Windows הוא Win32 או Win64, נקבע לפי רוחב הביטים של התהליך הרץ ולא של מערכת ההפעלה, כי זה מה שמכריע איזה בינארי ניתן לטעון. בכל מקום אחר השם נבנה מפקודות המאקרו של יעד המהדר כך שמכונה שבונה לשתי ארכיטקטורות מפיקה שני עצים מופרדים היטב, וכך שהתיקייה שמחזיקה את ספריית ה-native יושבת ליד התיקייה שמחזיקה את היחידות המקומפלות באותו שם
function BuildDllSubDir(UseV8: Boolean): string;
begin
{$IFDEF MSWINDOWS}
if IsWin64 then
Result := 'Win64'
else
Result := 'Win32';
{$ELSE}
// פקודות המאקרו של המהדר מאותגות את ה-OS ("Linux", "Darwin") בזמן שתיקיית
// פלט היחידות של החבילה אינה, כך שהשניים מסכימים רק אחרי
// קיפול רישיות. במערכת קבצים רגישת רישיות ההבדל הזה הוא
// כל החיפוש
Result := LowerCase({$I %FPCTARGETCPU%} + '-' + {$I %FPCTARGETOS%});
{$ENDIF}
end;
למה אות גדולה אחת שברה את השרשרת כולה
פקודת המאקרו של המהדר מאייתת את מערכת ההפעלה של היעד באות ראשונה גדולה: Win64, Linux, Darwin. החבילה של Lazarus כותבת את פלט היחידות שלה לתיקייה שנקראת ממשתנה היעד שלה עצמה, שהוא באותיות קטנות: win64, linux, darwin. שתי איותות של אותו דבר, ואי אפשר לשים לב ב-Windows, שבו מערכת הקבצים לא מבחינה ביניהן
ב-Linux הן שתי תיקיות שונות. פריסה שמציבה את ה-shared object ב-DLLs/x86_64-linux בלתי נראית לטוען שמחפש DLLs/x86_64-Linux, ולכן כל ארבעת השלבים המפורשים של השרשרת מפספסים והקוד נופל הלאה אל מתן לטוען של הפלטפורמה לחפש. לפעמים זה עובד, אם הספרייה במקרה מותקנת ברמת המערכת, ולפעמים לא, ובכל מקרה עץ הפריסה שסודר בקפידה אינו תורם דבר. לכשל אין הודעת שגיאה כי שום דבר לא נכשל: כל שלב דיווח נכון שהקובץ לא היה במקום שבו הוא הביט
תוכנית הבדיקה, מקומפלת ומרוצה
מחלקת באגים זו לא נמצאת בקריאה, והיא גם לא נמצאת בקימפול. הטכניקה הרגילה לאמת ענף פלטפורמה שלעולם לא מתקמפל על מכונת הפיתוח היא להעתיק את היחידה לתיקייה זמנית, לשנות את שמה, להחליף את התנאי הפלטפורמה בסימבול שלעולם לא מוגדר, ולקמפל את העותק; אם הוא מתקמפל, פסקת ה-uses וחתימות הקריאה באותו נתיב עקביים-עצמית לפחות. זה עובד היטב עבור יחידה עצמאית
זה לא עובד כאן. יחידת הקישור הראשית גדולה מאוד וגוררת את ה-LCL, ולכן אי אפשר פשוט להעתיק ולקמפל אותה עם סימבול ה-Windows כבוי. במקום זאת, הקומץ פונקציות שהשינוי נגע בהן שועתק מילה במילה אל תוכנית קטנה עצמאית, ואותה תוכנית הורצה. היא הדפיסה x86_64-Win64, ואי ההתאמה הייתה נראית בשורת פלט אחת. קימפול אותה תוכנית לא היה אומר דבר, כי המחרוזת תקינה לחלוטין; רק ערכה שגוי
program ProbeSubDir;
{$MODE DELPHI}
uses
SysUtils;
begin
// הדפיסו, אל תטענו. העניין הוא להביט בערך שפקודת מאקרו
// באמת מתרחבת אליו ב-toolchain הזה
Writeln('raw: ', {$I %FPCTARGETCPU%}, '-', {$I %FPCTARGETOS%});
Writeln('folded: ', LowerCase({$I %FPCTARGETCPU%} + '-' +
{$I %FPCTARGETOS%}));
end.
הלקח הכללי: כששינוי חוצה פלטפורמות נוגע ל-ערך של משהו ולא לטיפוס שלו, אימות של קימפול בלבד אינו אימות. הדפיסו אותו. הקבוצה הרחבה יותר של הבדלי מהדרים בין Delphi ל-Free Pascal נאספה במאמר מלכודות המהדרים החוצי הקומפילציה של Delphi ו-FPC
תנו לפלטפורמה להסביר את כשלי הטעינה שלה
ענף ה-Windows של הטוען מונה ביד את הסיבות שטעינה יכולה להיכשל בהן, כי ההבחנות השימושיות שם, אי התאמת ארכיטקטורה, תלות טרנזיטיבית חסרה, נתיב שאינו נפתר, ממופות לקודי שגיאה ששווה לקרוא להם בשם בנפרד. מחוץ ל-Windows יחידת הטוען הניידת כבר מחזירה מחרוזת תיאורית שמכסה את אותו שטח, ולכן הענף שאינו Windows משתמש בה ישירות במקום לגזור מחדש מחלקות ממספר שגיאה שפירושו שונה במערכות שונות
התנגדות לדחף לנרמל את שניהם להודעה אחת היא מכוונת. כשל טעינה הוא בעיית פריסה, והאדם שקורא את ההודעה צריך את אוצר המילים של הפלטפורמה עצמה כדי לחפש אותה
התנגשות שמות שקוראת לעצמה
עוד מלכודת אחת, קטנה וחדה. יחידת הטוען הניידת מייצאת פרוצדורה בשם UnloadLibrary, ויחידת הקישור חולקה פרוצדורה באותו שם שעושה את הרישום שלה עצמה לפני שחרור ה-handle. בתוך אותה פרוצדורה, קריאה לא ממוגרת ל-UnloadLibrary נפתרת לזו ביחידה הנוכחית, שקוראת לעצמה. התיקון הוא למגר את הקריאה בשם היחידה
זו אותה צורה כמו בעיות הצללת מזהים ששולטות בהעברות ל-Free Pascal באופן כללי: יחידת ה-Windows מייצאת פונקציות מינימום ומקסימום מטיפוס שלם שמצלילות את אלה של נקודה צפה, וטיפוס סנכרון שמצליל את המחלקה באותו שם, ובכל מקרה הפתרון תלוי בסדר פסקת ה-uses. מיגור אתר הקריאה הוא התיקון שאינו תלוי במישהו שישמר את הסדר הזה מאוחר יותר
רשימת בדיקה לפריסה
שלושה דברים מסבירים את רוב כשלי הטעינה ברגע שחשבון הנתיבים נכון. הארכיטקטורה חייבת להתאים לתהליך ולא למכונה, כך שאפליקציית 32 ביט על Windows בן 64 ביט צריכה את הבינארי בן 32 הביט. לבנייה עם V8 יש שם קובץ אחר, ולכן פריסה שמערבבת אותם תיראה נכונה ולא תטען דבר. ורק גרסה אחת יכולה לחיות בספריית מערכת בכל רגע, וזו סיבה טובה להעדיף את פריסת תת-הספרייה המפורשת על פני התקנה של כל דבר ברמת המערכת
עבור Lazarus בפרט, הציבו את ספריית ה-native תחת DLLs/<cpu>-<os> באותיות קטנות, ליד קובץ ההרצה, והיא תימצא על ידי השלב הראשון של השרשרת בכל יעד. דגימת ה-viewer שמפעילה זאת על Lazarus מתוארת במאמר ה-viewer של Lazarus ו-FPC, והתמיכה הנוכחית בפלטפורמות מופיעה בדף המוצר של PDFium Delphi component