סוגי הנתונים

הצהרות על נתוני HIDL יוצרות מבני נתונים בפריסה רגילה של C++‎. אפשר למקם את המבנים האלה בכל מקום שנראה טבעי (במקבץ, ברמת הקובץ או ברמת ה-global, או ב-heap), ואפשר ליצור אותם באותו אופן. קוד הלקוח קורא לקוד שרת העברה (proxy) של HIDL ומעביר הפניות const וסוגים פרימיטיביים, בעוד שקוד הסטאב וקוד השרת מסתירים את פרטי הסריאליזציה.

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

בטבלה הבאה מוצג מיפוי של פרימיטיבים של HIDL לסוגי נתונים של C++‎:

סוג HIDL סוג C++‎ Header/ספרייה
enum enum class
uint8_t..uint64_t uint8_t..uint64_t <stdint.h>
int8_t..int64_t int8_t..int64_t <stdint.h>
float float
double double
vec<T> hidl_vec<T> libhidlbase
T[S1][S2]...[SN] T[S1][S2]...[SN]
string hidl_string libhidlbase
handle hidl_handle libhidlbase
safe_union (custom) struct
struct struct
union union
fmq_sync MQDescriptorSync libhidlbase
fmq_unsync MQDescriptorUnsync libhidlbase

בקטעים הבאים מוסבר בהרחבה על סוגי הנתונים.

טיפוסים בני מנייה (enum)

enum ב-HIDL הופך ל-enum ב-C++. לדוגמה:

enum Mode : uint8_t { WRITE = 1 << 0, READ = 1 << 1 };
enum SpecialMode : Mode { NONE = 0, COMPARE = 1 << 2 };

… הופך ל-:

enum class Mode : uint8_t { WRITE = 1, READ = 2 };
enum class SpecialMode : uint8_t { WRITE = 1, READ = 2, NONE = 0, COMPARE = 4 };

החל מ-Android 10, אפשר להריץ חזרה על enum באמצעות ::android::hardware::hidl_enum_range. הטווח הזה כולל כל מונה באותו הסדר שבו הוא מופיע בקוד המקור של HIDL, החל ממונה ההורה ועד לצאצא האחרון. לדוגמה, הקוד הזה מבצע מחזור על WRITE, READ, NONE ו-COMPARE בסדר הזה. בהתאם ל-SpecialMode שלמעלה:

template <typename T>
using hidl_enum_range = ::android::hardware::hidl_enum_range<T>

for (SpecialMode mode : hidl_enum_range<SpecialMode>) {...}

hidl_enum_range מטמיע גם מְעַכְבֵי מַעֲרָכָה (iterators) הפוכים, וניתן להשתמש בו בהקשרים של constexpr. אם ערך מופיע במניין כמה פעמים, הוא מופיע בטווח כמה פעמים.

bitfield<T>

bitfield<T> (כאשר T הוא טיפוס enum שהוגדר על ידי משתמש) הופך לסוג הבסיסי של הטיפוס הזה ב-C++‎. בדוגמה שלמעלה, bitfield<Mode> הופך ל-uint8_t.

vec<T>

תבנית הכיתה hidl_vec<T> היא חלק מ-libhidlbase, וניתן להשתמש בה כדי להעביר וקטור מכל סוג HIDL בגודל שרירותי. המאגר המקביל בגודל קבוע הוא hidl_array. אפשר גם לאתחל את hidl_vec<T> כך שיצביע על מאגר נתונים חיצוני מסוג T באמצעות הפונקציה hidl_vec::setToExternal().

בנוסף להטמעה או להוספה של המבנה בצורה מתאימה בכותרת ה-C++ שנוצרת, השימוש ב-vec<T> יוצר כמה פונקציות נוחות לתרגום מ-std::vector ומ-T לבין נקודות הצבעה חשופות. אם משתמשים ב-vec<T> כפרמטר, הפונקציה שמשתמשת בו עוברת עומס יתר (נוצרים שני אב טיפוס) כדי לקבל ולהעביר גם את המבנה של HIDL וגם את סוג ה-std::vector<T> לפרמטר הזה.

מערך

מערכי קבועים ב-hidl מיוצגים על ידי הכיתה hidl_array ב-libhidlbase. הערך hidl_array<T, S1, S2, …, SN> מייצג מערך T[S1][S2]…[SN] בגודל קבוע של N מימדים.

מחרוזת

אפשר להשתמש בכיתה hidl_string (חלק מ-libhidlbase) כדי להעביר מחרוזות בממשקי HIDL, והיא מוגדרת ב-/system/libhidl/base/include/hidl/HidlSupport.h. מיקום האחסון הראשון בכיתה הוא הפניה למאגר התווים שלה.

hidl_string יודע להמיר מ-std::string and char* (מחרוזת בסגנון C) אליו ולהפך באמצעות operator=, המרות משתנים מרומזות והפונקציה .c_str(). למבנים של מחרוזות ב-HIDL יש את מפעילי ההקצאה והמפעילים המתאימים להעתקה כדי:

  • טעינת מחרוזת HIDL מ-std::string או ממחרוזת C.
  • יצירת std::string חדש ממחרוץ HIDL.

בנוסף, למחרוזות HIDL יש קונסטרוקטורים של המרות, כך שאפשר להשתמש במחרוזות C‏ (char *) ובמחרוזות C++‎ (std::string) בשיטות שמקבלות מחרוזת HIDL.

struct

struct ב-HIDL יכול להכיל רק סוגים של נתונים בגודל קבוע, ללא פונקציות. הגדרות המבנה של HIDL ממפות ישירות ל-struct ב-C++ עם פריסה רגילה, כדי להבטיח של-struct תהיה פריסה עקבית של זיכרון. מבנה יכול לכלול סוגי HIDL, כולל handle,‏ string ו-vec<T>, שמפנים למאגרים נפרדים באורך משתנה.

כינוי

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

הסוג handle מיוצג על ידי המבנה hidl_handle ב-C++, שהוא מעטפת פשוטה של הפניה לאובייקט const native_handle_t (הוא נמצא ב-Android כבר הרבה זמן).

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;

כברירת מחדל, hidl_handle לא מקבל בעלות על הסמן native_handle_t שהוא עוטף. הוא קיים רק כדי לאחסן באופן בטוח פוינטר ל-native_handle_t, כך שניתן יהיה להשתמש בו גם בתהליכים של 32 ביט וגם בתהליכים של 64 ביט.

תרחישים שבהם ל-hidl_handle יש בעלות על מתארי הקבצים המצורפים כוללים:

  • לאחר קריאה ל-method‏ setTo(native_handle_t* handle, bool shouldOwn) כשהפרמטר shouldOwn מוגדר ל-true
  • כשאובייקט hidl_handle נוצר על ידי יצירה באמצעות העתקה מאובייקט hidl_handle אחר
  • כשהקצאת העותק של אובייקט hidl_handle מתבצעת מאובייקט hidl_handle אחר

hidl_handle מספק המרות משתמעות ומפורשות לאובייקטים מסוג native_handle_t* . השימוש העיקרי בסוג handle ב-HIDL הוא להעביר מתארי קבצים דרך ממשקי HIDL. לכן, מתאר קובץ יחיד מיוצג על ידי native_handle_t ללא int ו-fd יחיד. אם הלקוח והשרת נמצאים בתהליך אחר, הטמעת ה-RPC מטפלת באופן אוטומטי בתיאור הקובץ כדי לוודא ששני התהליכים יכולים לפעול באותו קובץ.

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

זיכרון

הסוג memory ב-HIDL ממופה לכיתה hidl_memory ב-libhidlbase, שמייצגת זיכרון משותף לא ממופה. זהו האובייקט שצריך להעביר בין תהליכים כדי לשתף זיכרון ב-HIDL. כדי להשתמש בזיכרון משותף:

  1. מקבלים מכונה של IAllocator (כרגע רק המכונה 'ashmem' זמינה) ומשתמשים בה כדי להקצות זיכרון משותף.
  2. IAllocator::allocate() מחזירה אובייקט hidl_memory שאפשר להעביר דרך HIDL RPC ולמפות לתהליך באמצעות הפונקציה mapMemory של libhidlmemory.
  3. הפונקציה mapMemory מחזירה הפניה לאובייקט sp<IMemory> שאפשר להשתמש בו כדי לגשת לזיכרון. (הפונקציות IMemory ו-IAllocator מוגדרות בקטע android.hidl.memory@1.0).

אפשר להשתמש במכונה של IAllocator כדי להקצות זיכרון:

#include <android/hidl/allocator/1.0/IAllocator.h>
#include <android/hidl/memory/1.0/IMemory.h>
#include <hidlmemory/mapping.h>
using ::android::hidl::allocator::V1_0::IAllocator;
using ::android::hidl::memory::V1_0::IMemory;
using ::android::hardware::hidl_memory;
....
  sp<IAllocator> ashmemAllocator = IAllocator::getService("ashmem");
  ashmemAllocator->allocate(2048, [&](bool success, const hidl_memory& mem) {
        if (!success) { /* error */ }
        // now you can use the hidl_memory object 'mem' or pass it around
  }));

שינויים בפועל בזיכרון צריכים להתבצע דרך אובייקט IMemory, בצד שיצר את mem או בצד שמקבל אותו דרך HIDL RPC.

// Same includes as above

sp<IMemory> memory = mapMemory(mem);
void* data = memory->getPointer();
memory->update();
// update memory however you wish after calling update and before calling commit
data[0] = 42;
memory->commit();
// …
memory->update(); // the same memory can be updated multiple times
// …
memory->commit();

ממשק

אפשר להעביר ממשקים כאובייקטים. אפשר להשתמש במילה interface בתור סינטקס פשוט יותר לסוג android.hidl.base@1.0::IBase. בנוסף, הממשק הנוכחי וכל הממשקים המיובאים מוגדרים כסוג.

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