סוגי נתונים

סעיף זה מתאר סוגי נתונים HIDL. לפרטי יישום, ראה HIDL C++ (עבור יישומי C++) או HIDL Java (עבור יישומי Java).

קווי דמיון ל-C++ כוללים:

  • structs משתמשים בתחביר C++; unions תומכים בתחביר C++ כברירת מחדל. יש למנות את שניהם; מבנים ואיגודים אנונימיים אינם נתמכים.
  • Typedefs מותרים ב-HIDL (כמו שהם ב-C++).
  • הערות בסגנון C++ מותרות ומועתקות לקובץ הכותרת שנוצר.

קווי דמיון ל-Java כוללים:

  • עבור כל קובץ, HIDL מגדיר מרחב שמות בסגנון Java שעליו להתחיל ב- android.hardware. . מרחב השמות C++ שנוצר הוא ::android::hardware::… .
  • כל ההגדרות של הקובץ כלולות בתוך מעטפת interface בסגנון Java.
  • הצהרות מערך HIDL עוקבות אחר סגנון Java, לא בסגנון C++. דוגמה:
    struct Point {
        int32_t x;
        int32_t y;
    };
    Point[3] triangle;   // sized array
    
  • הערות דומות לפורמט javadoc.

ייצוג נתונים

struct או union המורכב מ- Standard-Layout (תת-קבוצה של הדרישה לסוגי נתונים רגילים-ישנים) יש פריסת זיכרון עקבית בקוד C++ שנוצר, שנאכפת עם תכונות יישור מפורשות על חברי struct union .

סוגי HIDL פרימיטיביים, כמו גם סוגי enum ו- bitfield (שתמיד נובעים מסוגים פרימיטיביים), ממפים לסוגי C++ סטנדרטיים כגון std::uint32_t מ- cstdint .

מכיוון ש-Java אינה תומכת בסוגים לא חתומים, סוגי HIDL לא חתומים ממופים לסוג ה-Java החתום המתאים. מבנים ממפים לשיעורי Java; מערכים ממפים למערכים של Java; איגודים לא נתמכים כרגע ב-Java. מחרוזות מאוחסנות באופן פנימי כ-UTF8. מאחר ש-Java תומכת רק במחרוזות UTF16, ערכי מחרוזת הנשלחים למימוש Java או ממנו מתורגמים, וייתכן שלא יהיו זהים בתרגום מחדש מכיוון שערכות התווים לא תמיד ממפות בצורה חלקה.

נתונים המתקבלים דרך IPC ב-C++ מסומנים const ונמצאים בזיכרון לקריאה בלבד שנמשך רק למשך קריאת הפונקציה. נתונים שהתקבלו דרך IPC ב-Java כבר הועתקו לאובייקטי Java, כך שניתן לשמור אותם ללא העתקה נוספת (וייתכן שישנו אותם).

הערות

ניתן להוסיף הערות בסגנון Java להצהרות סוג. ההערות מנותחות על ידי ה-Vendor Test Suite (VTS) הקצה האחורי של מהדר HIDL, אך אף אחת מההערות המנותחות הללו אינה מובנת למעשה על ידי המהדר HIDL. במקום זאת, הערות VTS ממנותחות מטופלות על ידי מהדר VTS (VTSC).

הערות משתמשות בתחביר Java: @annotation או @annotation(value) או @annotation(id=value, id=value…) כאשר הערך עשוי להיות ביטוי קבוע, מחרוזת או רשימת ערכים בתוך {} , בדיוק כמו ב Java. ניתן לצרף מספר הערות באותו שם לאותו פריט.

העבר הצהרות

ב-HIDL, אין להכריז על מבנים קדימה, מה שהופך את סוגי הנתונים המוגדרים על ידי המשתמש לבלתי אפשריים (לדוגמה, לא ניתן לתאר רשימה מקושרת או עץ ב-HIDL). לרוב ה-HALs הקיימים (טרום אנדרואיד 8.x) יש שימוש מוגבל בהצהרות קדימה, אותן ניתן להסיר על ידי ארגון מחדש של הצהרות מבנה הנתונים.

הגבלה זו מאפשרת להעתיק מבני נתונים לפי ערך בעזרת העתקה עמוקה פשוטה, במקום לעקוב אחר ערכי מצביע שעלולים להתרחש מספר פעמים במבנה נתונים עם הפניה עצמית. אם אותם נתונים מועברים פעמיים, כמו עם שני פרמטרים של שיטה או vec<T> s שמצביעים על אותם נתונים, נוצרים ונמסרים שני עותקים נפרדים.

הצהרות מקוננות

HIDL תומך בהצהרות מקוננות לכמה רמות שתרצה (למעט חריג אחד שצוין להלן). לדוגמה:

interface IFoo {
    uint32_t[3][4][5][6] multidimArray;

    vec<vec<vec<int8_t>>> multidimVector;

    vec<bool[4]> arrayVec;

    struct foo {
        struct bar {
            uint32_t val;
        };
        bar b;
    }
    struct baz {
        foo f;
        foo.bar fb; // HIDL uses dots to access nested type names
    }
    …

היוצא מן הכלל הוא שסוגי ממשק יכולים להיות מוטבעים רק ב- vec<T> ורק בעומק רמה אחת (ללא vec<vec<IFoo>> ).

תחביר מצביע גולמי

שפת HIDL אינה משתמשת ב-* ואינה תומכת בגמישות המלאה של מצביעי C/C++ גולמיים. לפרטים על האופן שבו HIDL מקפלת מצביעים ומערכים/וקטורים, ראה תבנית vec<T> .

ממשקים

למילת interface יש שני שימושים.

  • זה פותח את ההגדרה של ממשק בקובץ .hal.
  • זה יכול לשמש כסוג מיוחד בשדות struct/איחוד, פרמטרים של שיטה והחזרות. הוא נתפס כממשק כללי וכמילה נרדפת ל- android.hidl.base@1.0::IBase .

לדוגמה, IServiceManager יש את השיטה הבאה:

get(string fqName, string name) generates (interface service);

השיטה מבטיחה לחפש ממשק כלשהו בשם. זהה גם להחליף את הממשק ב- android.hidl.base@1.0::IBase .

ניתן להעביר ממשקים רק בשתי דרכים: כפרמטרים ברמה העליונה, או כחברים של vec<IMyInterface> . הם לא יכולים להיות חברים ב-vecs מקוננים, מבנים, מערכים או איגודים.

MQDescriptorSync ו-MQDescriptorUnsync

הסוגים MQDescriptorSync ו- MQDescriptorUnsync מעבירים מתארי תור הודעות מהיר מסונכרן או לא מסונכרן (FMQ) על פני ממשק HIDL. לפרטים, ראה HIDL C++ (FMQs אינם נתמכים ב-Java).

סוג זיכרון

סוג memory משמש לייצוג זיכרון משותף לא ממופה ב-HIDL. זה נתמך רק ב-C++. ניתן להשתמש בערך מסוג זה בצד המקבל כדי לאתחל אובייקט IMemory , מיפוי הזיכרון והפיכתו לשימוש. לפרטים, ראה HIDL C++ .

אזהרה: נתונים מובנים המוצבים בזיכרון המשותף חייבים להיות מסוג שהפורמט שלו לעולם לא ישתנה במשך כל החיים של גרסת הממשק העוברת את memory . אחרת, HALs עלולים לסבול מבעיות תאימות קטלניות.

סוג מצביע

סוג pointer מיועד לשימוש פנימי HIDL בלבד.

סוג תבנית bitfield<T>

bitfield<T> שבו T הוא enum המוגדר על ידי המשתמש מציע שהערך הוא bitwise-OR של ערכי ה-enum שהוגדרו ב- T בקוד שנוצר, bitfield<T> מופיע כסוג הבסיס של T. לדוגמה:

enum Flag : uint8_t {
    HAS_FOO = 1 << 0,
    HAS_BAR = 1 << 1,
    HAS_BAZ = 1 << 2
};
typedef bitfield<Flag> Flags;
setFlags(Flags flags) generates (bool success);

המהדר מטפל בסוג Flags זהה ל- uint8_t .

למה לא להשתמש (u)int8_t / (u)int16_t / (u)int32_t / (u)int64_t ? השימוש bitfield מספק מידע נוסף של HAL לקורא, שעכשיו יודע ש- setFlags לוקח ערך bitwise-OR של Flag (כלומר יודע שקריאה ל- setFlags עם 16 אינה חוקית). ללא bitfield , מידע זה מועבר רק באמצעות תיעוד. בנוסף, VTS יכול למעשה לבדוק אם הערך של הדגלים הוא bitwise-OR של Flag.

לטפל בסוג פרימיטיבי

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

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

native_handle_t

אנדרואיד תומך native_handle_t , מושג אחיזה כללי המוגדר ב- libcutils .

typedef struct native_handle
{
  int version;        /* sizeof(native_handle_t) */
  int numFds;         /* number of file-descriptors at &data[0] */
  int numInts;        /* number of ints at &data[numFds] */
  int data[0];        /* numFds + numInts ints */
} native_handle_t;

נקודת אחיזה מקורית היא אוסף של מתארי אינטס וקבצים שמועברים לפי ערך. ניתן לאחסן מתאר קובץ בודד בנקודת אחיזה מקורית ללא אינטס ומתאר קובץ בודד. העברת ידיות באמצעות ידיות מקוריות המובלעות בסוג הפרימיטיבי handle מבטיחה כי ידיות מקוריות נכללות ישירות ב-HIDL.

מכיוון של- native_handle_t יש גודל משתנה, לא ניתן לכלול אותו ישירות במבנה. שדה ידית יוצר מצביע ל- native_handle_t שהוקצה בנפרד.

בגירסאות קודמות של אנדרואיד, נקודות אחיזה מקוריות נוצרו באמצעות אותן פונקציות הקיימות ב- libcutils . באנדרואיד 8.0 ומעלה, הפונקציות הללו מועתקות כעת למרחב השמות android::hardware::hidl או מועברות ל-NDK. קוד שנוצר אוטומטית של HIDL מעביר פונקציות אלה בסידרה ומבטל את סדרתן באופן אוטומטי, ללא מעורבות מקוד שנכתב על ידי המשתמש.

טיפול בעלות מתאר קובץ

כאשר אתה קורא לשיטת ממשק HIDL שמעבירה (או מחזירה) אובייקט hidl_handle (ברמה עליונה או חלק מסוג מורכב), הבעלות על מתארי הקבצים הכלולים בו היא כדלקמן:

  • המתקשר המעביר אובייקט hidl_handle כארגומנט שומר על הבעלות על מתארי הקבצים הכלולים ב- native_handle_t שהוא עוטף; המתקשר חייב לסגור את מתארי הקבצים הללו כאשר הוא מסיים איתם.
  • התהליך המחזיר אובייקט hidl_handle (על ידי העברתו לפונקציה _cb ) שומר על הבעלות על מתארי הקבצים הכלולים ב- native_handle_t העוטף האובייקט; התהליך חייב לסגור את מתארי הקבצים האלה כשהוא נעשה איתם.
  • לטרנספורט שמקבל hidl_handle יש בעלות על מתארי הקבצים בתוך ה- native_handle_t שעוטף האובייקט; המקבל יכול להשתמש בתיאורי הקבצים האלה כפי שהם במהלך החזרת העסקה, אך עליו לשכפל את הידית המקורית כדי להשתמש במתארי הקבצים מעבר להתקשרות חזרה. הטרנספורט close() את מתארי הקובץ כאשר העסקה תבוצע.

HIDL אינו תומך בידיות ב-Java (כיוון ש-Java אינה תומכת בידיות כלל).

מערכים בגודל

עבור מערכים בגודל במבני HIDL, האלמנטים שלהם יכולים להיות מכל סוג שמבנה יכול להכיל:

struct foo {
uint32_t[3] x; // array is contained in foo
};

מחרוזות

מחרוזות מופיעות בצורה שונה ב-C++ וב-Java, אך סוג אחסון התחבורה הבסיסי הוא מבנה C++. לפרטים, ראה סוגי נתונים HIDL C++ או סוגי נתונים HIDL Java .

הערה: העברת מחרוזת ל-Java או ממנה דרך ממשק HIDL (כולל Java ל-Java) תגרום להמרות של ערכות תווים שאולי לא ישמרו בדיוק את הקידוד המקורי.

תבנית מסוג vec<T>

תבנית vec<T> מייצגת מאגר בגודל משתנה המכיל מופעים של T .

T יכול להיות אחד מהבאים:

  • טיפוסים פרימיטיביים (למשל uint32_t)
  • מחרוזות
  • סכומים המוגדרים על ידי המשתמש
  • מבנים המוגדרים על ידי המשתמש
  • ממשקים, או מילת המפתח interface ( vec<IFoo> , vec<interface> נתמכת רק כפרמטר ברמה העליונה)
  • ידיות
  • bitfield<U>
  • vec<U>, כאשר U נמצא ברשימה זו מלבד הממשק (למשל, vec<vec<IFoo>> אינו נתמך)
  • U[] (מערך בגודל של U), כאשר U נמצא ברשימה זו מלבד ממשק

סוגים מוגדרי משתמש

סעיף זה מתאר סוגים המוגדרים על ידי המשתמש.

Enum

HIDL אינה תומכת במינונים אנונימיים. אחרת, ערכים ב-HIDL דומים ל-C++11:

enum name : type { enumerator , enumerator = constexpr , …  }

Enum בסיס מוגדר במונחים של אחד מסוגי המספרים השלמים ב-HIDL. אם לא צוין ערך עבור המונה הראשון של מנה המבוסס על סוג מספר שלם, הערך מוגדר כברירת מחדל ל-0. אם לא צוין ערך עבור מונה מאוחר יותר, הערך יקבע כברירת מחדל את הערך הקודם פלוס אחד. לדוגמה:

// RED == 0
// BLUE == 4 (GREEN + 1)
enum Color : uint32_t { RED, GREEN = 3, BLUE }

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

// ULTRAVIOLET == 5 (Color:BLUE + 1)
enum FullSpectrumColor : Color { ULTRAVIOLET }

אזהרה: תורשת Enum פועלת לאחור מרוב סוגי הירושה האחרים. לא ניתן להשתמש בערך מנה צאצא בתור ערך מנה אב. הסיבה לכך היא שמניין צאצא כולל יותר ערכים מההורה. עם זאת, ניתן להשתמש בבטחה בערך ה-Enum של אב כערך ה-Enum של צאצא מכיוון שערכי ה-Enum של הבן הם בהגדרה קבוצה-על של ערכי ה-Enum של האב. זכור זאת בעת תכנון ממשקים מכיוון שמשמעות הדבר היא שסוגים המתייחסים ל-enums אב לא יכולים להתייחס ל-enums של צאצאים באיטרציות מאוחרות יותר של הממשק שלך.

ערכים של enums נקראים עם תחביר המעי הגס (לא תחביר נקודות כסוגים מקוננים). התחביר הוא Type:VALUE_NAME . אין צורך לציין סוג אם הערך מופנה באותו סוג ה-enum או אותו סוגי צאצא. דוגמא:

enum Grayscale : uint32_t { BLACK = 0, WHITE = BLACK + 1 };
enum Color : Grayscale { RED = WHITE + 1 };
enum Unrelated : uint32_t { FOO = Color:RED + 1 };

החל מאנדרואיד 10, ל-enums יש תכונה len שניתן להשתמש בה בביטויים קבועים. MyEnum::len הוא המספר הכולל של ערכים בספירה זו. זה שונה ממספר הערכים הכולל, שעשוי להיות קטן יותר כאשר ערכים משוכפלים.

מבנה

HIDL אינו תומך במבנים אנונימיים. אחרת, מבנים ב-HIDL דומים מאוד ל-C.

HIDL אינו תומך במבני נתונים באורך משתנה הכלולים במלואם בתוך מבנה. זה כולל את המערך באורך הבלתי מוגדר שלפעמים משמש כשדה האחרון של מבנה ב-C/C++ (לפעמים נראה בגודל של [0] ). HIDL vec<T> מייצג מערכים בגודל דינמי כאשר הנתונים מאוחסנים במאגר נפרד; מופעים כאלה מיוצגים עם מופע של vec<T> struct .

באופן דומה, string יכולה להיכלל struct (מאגרים משויכים נפרדים). ב-C++ שנוצר, מופעים מסוג נקודת האחיזה HIDL מיוצגים באמצעות מצביע אל נקודת האחיזה המקורית בפועל, שכן מופעים של סוג הנתונים הבסיסיים הם באורך משתנה.

הִתאַחֲדוּת

HIDL אינה תומכת באיגודים אנונימיים. אחרת, איגודים דומים ל-C.

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

union UnionType {
uint32_t a;
//  vec<uint32_t> r;  // Error: can't contain a vec<T>
uint8_t b;1
};
fun8(UnionType info); // Legal

ניתן להכריז גם על איגודים בתוך מבנים. לדוגמה:

struct MyStruct {
    union MyUnion {
      uint32_t a;
      uint8_t b;
    }; // declares type but not member

    union MyUnion2 {
      uint32_t a;
      uint8_t b;
    } data; // declares type but not member
  }