להגיע רחוק יותר: Peta::FFI לגרפיקה#

פרקי ערכות הכלים כיסו את מה ש־CPAN כבר עוטף. פרק זה, ושאר המדריך, הולכים רחוק יותר: הגעה לספריית גרפיקה של C שאין לה כריכת CPAN כלל, באמצעות ה־FFI המובנה של pperl. זוהי יכולת בלעדית ל־pperl. כריכת ספריית C בדרך המסורתית משמעה כתיבת shim ב־C, הידורו, ומשלוח שרשרת־כלי בנייה עם הקוד שלכם; pperl קורא לספריית ה־C ישירות, אותו מנגנון שכבר נותן לכם את Math::GMP בלי שום כריכה מהודרת לבנות כלל.

זה אינו FFI::Platypus. Peta::FFI מובנה בזמן הריצה, והכריכות האצורות שלו הן Rust מובנה, לא הצהרות בצד Perl. ההבחנה חשובה לגרפיקה מפני שכריכת גרפיקה אמיתית צריכה לנהל אובייקטים שהוקצו על ידי C (משטח, הקשר) לאורך קריאות רבות, והמודל של pperl מטפל בזה כפי שספריית ה־C עצמה עושה.

העיון המוסמך לשכבת ה־FFI הוא עמוד Auto-FFI; פרק זה מיישם אותו על גרפיקה באופן ספציפי.

שלוש הרמות#

את Peta::FFI הכי טוב להבין כשלוש רמות, מגולמי לאצור.

רמה 1, קריאות גולמיות. אתם מכנים ספרייה, פונקציה ומחרוזת חתימת־טיפוס, ו־pperl משגר את הקריאה דרך libffi. שום דבר אינו נוצר מראש; כל חתימה שאתם יכולים לאיית עובדת.

use Peta::FFI qw(dlopen call dlclose);

my $lib = dlopen("libm.so.6");
my $r = call($lib, "sqrt", "(d)d", 2.0);     # sqrt(2) = 1.4142...
dlclose($lib);

החתימה "(args)ret" משתמשת באות אחת לכל טיפוס: v void, i int, l long, L unsigned long או size_t, d double, f float, p מחרוזת C נכנסת, ו־P חוצץ פלט בר־שינוי. הקבוצה הזו מכסה כמות מפתיעה של API של C: כל דבר שהארגומנטים והערך המוחזר שלו הם סקלרים, מחרוזות, או חוצץ בתים.

רמה 2, שכבת תיאור־נתונים. פונקציות C רבות מקבלות או מחזירות structs, לא סקלרים, וקודי הטיפוס הגולמיים אינם יכולים לתאר את פריסת ה־struct. התשובה המתוכננת היא שכבה הצהרתית שבה מתארים את שדות הרשומה פעם אחת ומאפשרים ל־FFI לשנע אותה. זהו נתיב הביניים המתהווה; הוא עדיין אינו מודול שנשלח, ומדריך זה מפורש בכל נקודה לגבי היכן כריכה תזדקק לו.

רמה 3, כריכות אצורות. הרמה העליונה היא כריכה מובנית כתובה־ביד: מודול Rust שפותח את הספרייה פעם אחת, מַתְאִים כל סמל הדרוש לו, ומציג API נקי של Perl, תוך ניהול האובייקטים שהוקצו על ידי C מאחורי ערכי Perl. Math::GMP הוא בדיוק זה: use Math::GMP ואתם מקבלים מספרים שלמים בעלי דיוק שרירותי עם העמסת אופרטורים, ובשום מקום לא נגעתם ב־dlopen או במחרוזת חתימה. הפרק הבא לוקח אחת מאלה ש־pperl כבר שולח עבור ספריית גרפיקה, Cairo, ומראה כיצד היא עובדת.

למה קריאות גולמיות יכולות ולא יכולות להגיע#

הקו המפריד עבור גרפיקה הוא ה־struct. חלק גדול מ־API גרפי של C נגיש ברמה 1 כבר עכשיו, מפני שהוא סקלרים וידיות אטומות:

  • פונקציות שמקבלות ומחזירות מספרים (הגדרת רוחב קו, רדיוס, רכיב צבע) ממופות ישירות אל i, d, f וחבריהם.

  • פונקציות שמקבלות מחרוזת C (טעינת גופן לפי שם, פתיחת קובץ) משתמשות ב־p.

  • פונקציות שממלאות חוצץ שסופק על ידי הקורא (קריאת פיקסלים אל זיכרון שבבעלותכם) משתמשות ב־P.

  • ידית אטומה המוחזרת על ידי הספרייה (מצביע משטח, מצביע הקשר) היא בסך הכול כתובת. pperl כבר מייצג ידית ספרייה כמספר שלם, והתבנית האצורה בפרק הבא מאחסנת את ידיות האובייקטים האלה באותו אופן: כמספר שלם בתוך הפניית Perl מבורכת.

היכן קריאות גולמיות עוצרות:

  • Structs המועברים או מוחזרים לפי ערך. פונקציה המקבלת Color { r, g, b, a } לפי ערך, או המחזירה Rectangle, אינה ניתנת לתיאור באמצעות קודי הטיפוס הסקלריים. זהו פער רמה 2.

  • Callbacks. ספרייה שקוראת בחזרה אל הקוד שלכם (לולאת אירועים המפעילה מטפל, אות ציור) זקוקה למצביע פונקציה של C שנכנס מחדש אל Perl. זה מעבר לשכבה הגולמית.

  • Unions. טיפוס אירוע שהוא union של צורות struct רבות (רשומת אירוע החלונות הקלאסית) משלב את שתי הבעיות לעיל.

אלה אינם קירות קבועים; הם החזית שאליה שכבות ה־FFI גדלות. ערכו של מדריך זה הוא לומר לכם בדיוק באיזה צד של הקו נופלת משימה נתונה, כך שלא תימנעו ממה שעובד ולא תבטיחו את מה שלא.

מדוע כריכה אצורה, ולא קריאות גולמיות, לעבודה אמיתית#

תוכלו להפעיל ספריית גרפיקה כולה דרך callים של רמה 1, ולניסוי מהיר זה בסדר. לכל דבר אמיתי, כריכה אצורה של רמה 3 היא היחידה הנכונה, משלוש סיבות.

ראשית, משך חיי אובייקט. הקשר גרפיקה מוקצה על ידי הספרייה וחייב להשתחרר בדיוק פעם אחת. כריכה אצורה קושרת את משך החיים הזה ל־DESTROY של ערך Perl, כך שאובייקט ה־C משתחרר כשאובייקט ה־Perl נעלם, ללא ניהול־ספרים ידני. קריאות גולמיות משאירות זאת כולו לכם.

ארגונומיה של Perl ממדרגה ראשונה היא הסיבה השנייה: $surface->fill נקרא טוב יותר וקשה יותר לטעות בו מאשר call עם חתימה כתובה־ביד בכל אתר שימוש.

שלישית, החתימה נכתבת פעם אחת. בכריכה אצורה מחרוזות ה־"(ppd)i" חיות במודול Rust אחד, נבדקות פעם אחת; כל קורא פשוט משתמש במתודה. פיזור חתימות גולמיות לאורך קוד היישום הוא כיצד מקבלים אי־התאמה שמשחיתה את המחסנית.

תצוגת Cairo הופכת את שלושתם למוחשיים עם כריכת ה־Cairo ש־pperl שולח, ספריית ווקטור אמיתית שה־API שלה הוא כמעט כולו סקלרים וידית אטומה אחת, וזו בדיוק הצורה שמגיעה בנקיות היום.

צורת מה שבא בהמשך#

הפרק הבא בונה כריכת Cairo אצורה באופן ש־Math::GMP בנוי, כך שתוכלו לראות ספריית גרפיקה מובאת על pperl מקצה לקצה. אחר כך שני פרויקטי הגולה משתמשים במחסנית ערכות הכלים המתוחזקת מקשת B, מעוגנת על Prima, כדי לבנות יישום ציור ומשחק דו־ממדי שאפשר להריץ, כשחוט ה־FFI ארוג פנימה היכן שהוא זוכה במקומו ומסומן בכנות היכן החזית מתחילה. הפרק האחרון משרטט את מפת מה שנותר.