הצהרות על נתוני 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. כדי להשתמש בזיכרון משותף:
- מקבלים מכונה של
IAllocator
(כרגע רק המכונה 'ashmem' זמינה) ומשתמשים בה כדי להקצות זיכרון משותף. IAllocator::allocate()
מחזירה אובייקטhidl_memory
שאפשר להעביר דרך HIDL RPC ולמפות לתהליך באמצעות הפונקציהmapMemory
שלlibhidlmemory
.- הפונקציה
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<>
.