סוגי נתונים

הצהרות נתונים מסוג 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. שפת תרגום משתמשים בזיכרון המשותף:

  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<>.