סוגי נתונים

הצהרות נתוני 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() .

בנוסף לפליטת/הכנסת ה-struct כראוי בכותרת 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.
  • צור 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 (זה קיים באנדרואיד כבר זמן רב).

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. כדי להשתמש בזיכרון משותף:

  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();

מִמְשָׁק

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

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