הצהרות נתונים מסוג HIDL יוצרות מבני נתונים בפריסה רגילה של C++. האלה אפשר למקם אותם בכל מקום שמרגיש טבעי (על הערימה, הקובץ או בהיקף גלובלי או בערימה) ואפשר להלחין אותו באותו אופן. לקוח/ה מפעילי קוד HIDL ומעבירים הפניות קבועות וסוגים פרימיטיביים, בזמן שקוד ה-stub ו-Proxy מסתיר את פרטי העריכה הסידורית.
הערה: הקוד לא נכתב על ידי המפתח שנדרשת כדי לבצע סריאליזציה או deserialize של מבני נתונים באופן מפורש.
הטבלה הבאה ממפה פרימיטיבים 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
.
ו<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_array
ב-libhidlbase
. hidl_array<T, S1, S2, …,
SN>
מייצג מערך בגודל קבוע של N
T[S1][S2]…[SN]
.
String (מחרוזת)
הכיתה hidl_string
(חלק מ-libhidlbase
) יכולה להיות
משמשת להעברת מחרוזות דרך ממשקי HIDL ומוגדרת
/system/libhidl/base/include/hidl/HidlSupport.h
. נפח האחסון הראשון
המיקום במחלקה הוא מצביע על מאגר התווים.
hidl_string
יודע איך לבצע המרה ומקור
std::string and char*
(מחרוזת בסגנון C) באמצעות
operator=
, הפעלות מרומזות (cast) והפונקציה .c_str()
.
למבני מחרוזת HIDL יש את בוני ההעתקה וההקצאה המתאימים
אופרטורים של:
- טוענים את המחרוזת HIDL מ-
std::string
או ממחרוזת C. - יצירת
std::string
חדש ממחרוזת HIDL.
בנוסף, למחרוזות HIDL יש בנאים של המרות, כך שמחרוזות C.
אפשר להשתמש במחרוזות (char *
) ובמחרוזות C++ (std::string
)
שיטות שבהן לוקחים מחרוזת HIDL.
לבנות
struct
ב-HIDL יכול להכיל רק סוגי נתונים בגודל קבוע ולא
למשימות ספציפיות. ההגדרות של מבנה HIDL ממופות ישירות לפריסה רגילה
struct
ב-C++, כדי לוודא שלstruct
יש
פריסת זיכרון עקבית. מבנה יכול לכלול סוגי HIDL, כולל
handle
, string
וגם vec<T>
,
להצביע על מאגרי נתונים זמניים בעלי אורך משתנה.
כינוי
אזהרה: כתובות מכל סוג (גם כתובות פיזיות) כתובות מכשיר) אף פעם לא יכולות להיות חלק מכינוי מקורי. העברתי המידע בין התהליכים מסוכן וחשוף לתקיפות. צריך לאמת את כל הערכים שמועברים בין תהליכים לפני שמשתמשים בהם לחיפוש זיכרון מוקצה בתוך תהליך. אחרת, כינויים לא תקינים עלולים לגרום גישה לזיכרון או פגיעה בזיכרון.
הסוג handle
מיוצג על ידי hidl_handle
ב-C++, שהוא wrapper פשוט סביב מצביע
אובייקט 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
מוצגות המרות מרומזות והמרות מפורשות
to/from native_handle_t*
objects. השימוש העיקרי עבור
סוג handle
ב-HIDL הוא העברת מתארי קבצים באמצעות HIDL
ממשקים. לכן מתאר קובץ יחיד מיוצג על ידי
native_handle_t
ללא int
וסינגל
fd
. אם הלקוח והשרת נמצאים בתהליך שונה, ה-RPC
יטפל אוטומטית במתאר הקובץ כדי לוודא
שני התהליכים יכולים לפעול על אותו קובץ.
למרות שמתאר קובץ התקבל ב-hidl_handle
על ידי
תקף בתהליך הזה, הוא לא נשמר מעבר
הפונקציה (היא נסגרת כשהפונקציה חוזרת). תהליך שמטרתו
לשמור גישה קבועה למתאר הקובץ חייבת dup()
מתארי קבצים מצורפים, או מעתיקים את האובייקט hidl_handle
בשלמותו.
זיכרון
סוג HIDL memory
ממופה למחלקה 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<>
.