הצהרות נתוני HIDL מייצרות מבני נתונים בפריסה סטנדרטית של C++. ניתן למקם את המבנים הללו בכל מקום שמרגיש טבעי (על הערימה, בקובץ או בהיקף גלובלי, או על הערימה) וניתן להרכיב אותם באותה צורה. קוד לקוח קורא לקוד פרוקסי HIDL העובר בהפניות קונסטטיביות וסוגים פרימיטיביים, בעוד שקוד הפרוקסי מסתיר את פרטי ההסדרה.
הערה: בשום שלב לא נדרש קוד שנכתב על ידי המפתח כדי לבצע במפורש סדרה או דה-סריאליזציה של מבני נתונים.
הטבלה שלהלן ממפה פרימיטיביות HIDL לסוגי נתונים C++:
סוג HIDL | סוג C++ | כותרת/ספרייה |
---|---|---|
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
מיישם גם איטרטורים הפוכים וניתן להשתמש בו בהקשרים של constexpr
. אם ערך מופיע בספירה מספר פעמים, הערך מופיע בטווח מספר פעמים.
bitfield<T>
bitfield<T>
(כאשר T
הוא enum המוגדר על ידי המשתמש) הופך לסוג הבסיסי של אותו 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
bare. אם ה- vec<T>
משמש כפרמטר, הפונקציה המשתמשת בו תועמס יתר על המידה (נוצרו שני אבות טיפוס) כדי לקבל ולהעביר גם את מבנה HIDL וגם סוג std::vector<T>
עבור פרמטר זה.
מַעֲרָך
מערכים קבועים ב-hidl מיוצגים על ידי המחלקה hidl_array
ב- libhidlbase
. hidl_array<T, S1, S2, …, SN>
מייצג מערך N בגודל קבוע T[S1][S2]…[SN]
.
חוּט
המחלקה hidl_string
(חלק מ- libhidlbase
) יכולה לשמש להעברת מחרוזות על פני ממשקי HIDL והיא מוגדרת ב- /system/libhidl/base/include/hidl/HidlSupport.h
. מיקום האחסון הראשון במחלקה הוא מצביע למאגר התווים שלו.
hidl_string
יודע להמיר וממנו std::string and char*
(מחרוזת בסגנון C) באמצעות operator=
, casts implicit .c_str()
. למבני מחרוזת HIDL יש את הבנאי העתקה ואופרטורי ההקצאה המתאימים ל:
- טען את המחרוזת HIDL ממחרוזת
std::string
או מחרוזת C. - צור סטd
std::string
חדש ממחרוזת HIDL.
בנוסף, למחרוזות HIDL יש בנוני המרה כך שניתן להשתמש במחרוזות C ( char *
) ו-C++ ( std::string
) בשיטות שלוקחות מחרוזת HIDL.
מבנה
struct
ב-HIDL יכול להכיל רק סוגי נתונים בגודל קבוע וללא פונקציות. הגדרות מבנה struct
ממפות ישירות למבני פריסה סטנדרטיים ב-C++, מה שמבטיח struct
יש פריסת זיכרון עקבית. מבנה יכול לכלול סוגי HIDL, כולל handle
, string
ו- vec<T>
, המצביעים על חוצצים נפרדים באורך משתנה.
ידית
אזהרה: כתובות מכל סוג שהוא (אפילו כתובות של מכשירים פיזיים) לעולם לא חייבות להיות חלק מהידית המקורית. העברת מידע זה בין תהליכים מסוכנת והופך אותם לרגישים להתקפות. כל ערכים המועברים בין תהליכים חייבים לעבור אימות לפני השימוש לחיפוש זיכרון שהוקצה בתוך תהליך. אחרת, ידיות לא טובות עלולות לגרום לגישה לקויה לזיכרון או לפגיעה בזיכרון.
סוג handle
מיוצג על ידי מבנה hidl_handle
ב-C++, שהוא עטיפה פשוטה סביב מצביע לאובייקט const native_handle_t
(זה קיים באנדרואיד כבר זמן רב).
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
אכן הבעלים של מתארי הקבצים הסגורים שלו כוללים:
- בעקבות קריאה
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
s ו- fd
בודד. אם הלקוח והשרת חיים בתהליך שונה, הטמעת RPC תטפל אוטומטית במתאר הקובץ כדי להבטיח ששני התהליכים יכולים לפעול על אותו קובץ.
למרות שמתאר קובץ שהתקבל ב- hidl_handle
על ידי תהליך יהיה תקף בתהליך זה, הוא לא ימשיך מעבר לפונקציה המקבלת (הוא ייסגר ברגע שהפונקציה תחזור). תהליך שרוצה לשמור על גישה מתמשכת למתאר הקובץ חייב dup()
את מתארי הקובץ המצורפים, או להעתיק את כל האובייקט hidl_handle
.
זיכרון
סוג memory
HIDL ממפה למחלקה hidl_memory
ב- libhidlbase
, המייצגת זיכרון משותף לא ממופה. זהו האובייקט שיש להעביר בין תהליכים כדי לשתף זיכרון ב-HIDL. כדי להשתמש בזיכרון משותף:
- השג מופע של
IAllocator
(כרגע רק מופע "ashmem" זמין) והשתמש בו כדי להקצות זיכרון משותף. -
IAllocator::allocate()
מחזיר אובייקטhidl_memory
שניתן להעביר דרך HIDL RPC ולמפות אותו לתהליך באמצעותlibhidlmemory
שלmapMemory
. -
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();
מִמְשָׁק
ניתן להעביר ממשקים כאובייקטים. המילה ממשק יכולה לשמש כסוכר תחבירי עבור הסוג android.hidl.base@1.0::IBase
; בנוסף, הממשק הנוכחי וכל הממשקים המיובאים יוגדרו כסוג.
משתנים שמחזיקים ממשקים צריכים להיות מצביעים חזקים: sp<IName>
. פונקציות HIDL שלוקחות פרמטרים של ממשק ימירו מצביעים גולמיים למצביעים חזקים, ויגרמו להתנהגות לא אינטואיטיבית (ניתן לנקות את המצביע באופן בלתי צפוי). כדי למנוע בעיות, אחסן תמיד ממשקי HIDL בתור sp<>
.