انواع داده

این بخش انواع داده HIDL را شرح می‌دهد. برای جزئیات پیاده‌سازی، به HIDL C++‎ (برای پیاده‌سازی‌های C++‎) یا HIDL Java (برای پیاده‌سازی‌های Java) مراجعه کنید.

شباهت‌های C++ شامل موارد زیر می‌شود:

  • structs از نحو C++‎ استفاده می‌کند؛ unions به‌طور پیش‌فرض از نحو C++‎ پشتیبانی می‌کند. هر دو باید نام‌گذاری شوند؛ ساختارها و اتحادیه‌های ناشناس پشتیبانی نمی‌شوند.
  • Typedefs در HIDL مجاز است (همان‌طور که در C++ مجاز است).
  • نظرات سبک C++‎ مجاز هستند و در فایل سرصفحه تولیدشده کپی می‌شوند.

شباهت‌های آن با Java شامل موارد زیر است:

  • برای هر فایل، HIDL فضای نامی به سبک جاوا تعریف می‌کند که باید با android.hardware. شروع شود. فضای نام C++ تولیدشده این است: ::android::hardware::….
  • همه تعاریف فایل در یک پوشاننده interface به سبک جاوا قرار دارند.
  • اعلان‌های آرایه HIDL از سبک Java پیروی می‌کنند، نه سبک C++. مثال:
    struct Point {
        int32_t x;
        int32_t y;
    };
    Point[3] triangle;   // sized array
  • نظرات مشابه قالب javadoc هستند.

نمایش داده‌ها

struct یا union متشکل از چیدمان استاندارد (زیرمجموعه‌ای از الزامات انواع داده‌های ساده قدیمی) در کد C++ تولیدشده چیدمان حافظه یکسانی دارد که با ویژگی‌های تراز صریح در اعضای struct و union اعمال می‌شود.

انواع HIDL اولیه، و همچنین انواع enum و bitfield (که همیشه از انواع اولیه مشتق می‌شوند)، به انواع استاندارد ++C مانند std::uint32_t از cstdint نگاشت می‌شوند.

ازآنجایی‌که Java از انواع بدون علامت پشتیبانی نمی‌کند، انواع بدون علامت HIDL به نوع Java علامت‌دار مربوطه نگاشت می‌شوند. ساختارها به کلاس‌های جاوا نگاشت می‌شوند؛ آرایه‌ها به آرایه‌های جاوا نگاشت می‌شوند؛ اتحادیه‌ها درحال‌حاضر در جاوا پشتیبانی نمی‌شوند. رشته‌ها به‌صورت داخلی به‌عنوان UTF8 ذخیره می‌شوند. ازآنجایی‌که Java فقط از رشته‌های UTF16 پشتیبانی می‌کند، مقادیر رشته‌ای که به یا از پیاده‌سازی Java ارسال می‌شوند ترجمه می‌شوند و ممکن است در ترجمه مجدد یکسان نباشند زیرا مجموعه‌های نویسه همیشه به‌طور یکپارچه نگاشت نمی‌شوند.

داده‌های دریافتی ازطریق IPC در C++‎ با const علامت‌گذاری شده‌اند و در حافظه فقط خواندنی قرار دارند که فقط درطول فراخوانی تابع باقی می‌ماند. داده‌های دریافت‌شده ازطریق IPC در Java قبلاً در اشیای Java کپی شده است، بنابراین می‌توان آن را بدون کپی کردن اضافی حفظ کرد (و می‌توان آن را تغییر داد).

حاشیه‌نویسی‌ها

حاشیه‌نویسی‌های سبک Java را می‌توان به اعلان‌های نوع اضافه کرد. گزارمان‌ها توسط زیرینه «مجموعه آزمایشی فروشنده» (VTS) کامپایلر HIDL تجزیه می‌شوند اما هیچ‌یک از این گزارشمان‌های تجزیه‌شده درواقع توسط کامپایلر HIDL درک نمی‌شوند. درعوض، حاشیه‌نویسی‌های تجزیه‌شده VTS توسط «کامپایلر VTS»‏ (VTSC) مدیریت می‌شود.

گزارمان‌ها از نحو Java استفاده می‌کنند: @annotation یا @annotation(value) یا @annotation(id=value, id=value…) که در آن مقدار می‌تواند عبارت ثابت، رشته، یا فهرستی از مقادیر درون {} باشد، درست مثل Java. چندین گزارمان با نام یکسان می‌تواند به یک مورد پیوست شود.

اظهارنامه‌های بازارسال

در HIDL، ممکن است ساختارها ازپیش اعلام نشوند، که باعث می‌شود انواع داده‌های خودارجاعی تعریف‌شده توسط کاربر غیرممکن شوند (برای مثال، نمی‌توانید یک فهرست پیوندی یا یک درخت را در HIDL توصیف کنید). بیشتر «لایه‌های انتزاع سخت‌افزار» موجود (پیش‌از Android نسخه ۸.x) استفاده محدودی از اعلان‌های پیش‌رونده دارند که می‌توان با بازآرایی اعلان‌های ساختار داده آن‌ها را حذف کرد.

این محدودیت به ساختارهای داده اجازه می‌دهد که با یک کپی عمیق ساده براساس مقدار کپی شوند، به‌جای اینکه مقادیر اشاره‌گری را که ممکن است چندین بار در یک ساختار داده خودارجاعی رخ دهند پیگیری کند. اگر داده‌های یکسانی دوبار ارسال شود، مثلاً با دو پارامتر روش یا vec<T>هایی که به داده‌های یکسان اشاره می‌کنند، دو نسخه جداگانه ساخته و ارسال می‌شود.

اظهارنامه‌های تودرتو

‫HIDL از بیانیه‌های تودرتو در هر سطحی که بخواهید پشتیبانی می‌کند (با یک استثنا که در زیر ذکر شده است). برای مثال:

interface IFoo {
    uint32_t[3][4][5][6] multidimArray;

    vec<vec<vec<int8_t>>> multidimVector;

    vec<bool[4]> arrayVec;

    struct foo {
        struct bar {
            uint32_t val;
        };
        bar b;
    }
    struct baz {
        foo f;
        foo.bar fb; // HIDL uses dots to access nested type names
    }
    …

استثنا این است که انواع رابط فقط می‌توانند در vec<T> جاسازی شوند و فقط یک سطح عمیق (بدون vec<vec<IFoo>>).

نحو اشاره‌گر خام

زبان HIDL از * استفاده نمی‌کند و از انعطاف‌پذیری کامل اشاره‌گرهای خام C/C++ پشتیبانی نمی‌کند. برای جزئیات بیشتر درباره نحوه کپسوله‌سازی اشاره‌گرها و آرایه‌ها/بردارها توسط HIDL، به الگوی vec<T> مراجعه کنید.

میانه‌ها

کلیدواژه interface دو کاربرد دارد.

  • تعریف میانای یک فایل ‎ .hal را باز می‌کند.
  • می‌توان از آن به‌عنوان نوعی خاص در فیلدهای struct/union، پارامترهای روش، و بازگشت‌ها استفاده کرد. این به‌عنوان یک رابط عمومی و مترادف با android.hidl.base@1.0::IBase درنظر گرفته می‌شود.

برای مثال، IServiceManager روش زیر را دارد:

get(string fqName, string name) generates (interface service);

این روش قول می‌دهد که برخی‌از میاناهای را براساس نام جستجو کند. همچنین با جایگزین کردن میانای با android.hidl.base@1.0::IBase یکسان است.

میان‌ها فقط به دو روش می‌توانند منتقل شوند: به‌عنوان پارامترهای سطح بالا، یا به‌عنوان اعضای vec<IMyInterface>. آن‌ها نمی‌توانند عضو vecs، structs، arrays، یا unions تودرتو باشند.

MQDescriptorSync و MQDescriptorUnsync

انواع MQDescriptorSync و MQDescriptorUnsync توصیف‌گرهای «صف پیام سریع» (FMQ) همگام‌سازی‌شده یا همگام‌سازی‌نشده را ازطریق میانای HIDL منتقل می‌کنند. برای جزئیات، به HIDL C++‎ مراجعه کنید (FMQ در Java پشتیبانی نمی‌شود).

نوع حافظه

نوع memory برای نمایش حافظه مشترک نگاشت‌نشده در HIDL استفاده می‌شود. این نوع فقط در C++‎ پشتیبانی می‌شود. در انتهای دریافت می‌توان از مقدار این نوع برای مقداردهی اولیه شیء IMemory استفاده کرد، حافظه را نگاشت، و آن را قابل‌استفاده کرد. برای جزئیات، به HIDL C++‎ مراجعه کنید.

هشدار: داده‌های ساختاریافته‌ای که در حافظه مشترک قرار می‌گیرند باید از نوعی باشند که قالب آن‌ها درطول عمر نسخه میانای گذرنده از memory هرگز تغییر نکند. درغیراین‌صورت، HALها ممکن است با مشکلات سازگاری مهلکی مواجه شوند.

نوع اشاره‌گر

نوع pointer فقط برای استفاده داخلی HIDL است.

الگوی نوع bitfield<T>

bitfield<T> که در آن T یک تعداد شمارشی تعریف‌شده توسط کاربر است نشان می‌دهد مقدار یک OR بیتی از مقادیر شمارشی تعریف‌شده در T است. در کد تولیدشده، bitfield<T> به‌عنوان نوع زیربنایی T ظاهر می‌شود. برای مثال:

enum Flag : uint8_t {
    HAS_FOO = 1 << 0,
    HAS_BAR = 1 << 1,
    HAS_BAZ = 1 << 2
};
typedef bitfield<Flag> Flags;
setFlags(Flags flags) generates (bool success);

گردآورنده نوع «پرچم‌ها» را همانند uint8_t مدیریت می‌کند.

Why not use (u)int8_t/(u)int16_t/(u)int32_t/(u)int64_t? Using bitfield provides additional HAL information to the reader, who now knows that setFlags takes a bitwise-OR value of Flag (i.e. knows that calling setFlags with 16 is invalid). بدون bitfield، این اطلاعات فقط ازطریق اسناد منتقل می‌شود. علاوه‌براین، VTS می‌تواند بررسی کند که آیا مقدار پرچم‌ها یک OR بیتی از Flag است یا خیر.

دستگیره‌های نوع ابتدایی

هشدار: نشانی‌های از هر نوع (حتی نشانی‌های دستگاه فیزیکی) هرگز نباید بخشی از نام کاربری بومی باشند. انتقال این اطلاعات بین فرایندها خطرناک است و آن‌ها را دربرابر حمله آسیب‌پذیر می‌کند. هر مقداری که بین فرایندها منتقل می‌شود باید قبل‌از استفاده برای جستجوی حافظه اختصاص‌داده‌شده در فرایند، اعتبارسنجی شود. درغیراین‌صورت، دستگیره‌های بد ممکن است باعث دسترسی بد به حافظه یا خراب شدن حافظه شوند.

معناشناسی HIDL براساس کپی با مقدار است، که به این معنی است که پارامترها کپی می‌شوند. هرگونه داده بزرگ یا داده‌ای که باید بین فرایندها هم‌رسانی شود (مثل حصار همگام‌سازی)، با انتقال توصیفگرهای فایل که به اشیاء ماندگار اشاره می‌کنند مدیریت می‌شود: ashmem برای حافظه مشترک، فایل‌های واقعی، یا هر چیز دیگری که می‌تواند پشت توصیفگر فایل پنهان شود. گرداننده چسباننده توصیفگر فایل را در فرایند دیگر تکثیر می‌کند.

native_handle_t

‫Android از native_handle_t، مفهوم عمومی نام کاربری تعریف‌شده در libcutils پشتیبانی می‌کند.

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;

دستگیره بومی مجموعه‌ای از اعداد صحیح و توصیف‌گرهای فایل است که براساس مقدار منتقل می‌شود. توصیفگر فایل تکی را می‌توان در دستگیره بومی با بدون عدد صحیح و یک توصیفگر فایل تکی ذخیره کرد. انتقال دسته‌ها بااستفاده از دسته‌های بومی کپسوله‌شده با نوع اولیه handle تضمین می‌کند که دسته‌های بومی مستقیماً در HIDL گنجانده شوند.

چون native_handle_t اندازه متغیر دارد، نمی‌تواند مستقیماً در ساختار گنجانده شود. فیلد دستگیره اشاره‌گری به native_handle_t اختصاص‌یافته به‌صورت جداگانه تولید می‌کند.

در نسخه‌های قبلی Android، دسته‌های بومی بااستفاده از همان توابع موجود در libcutils ایجاد می‌شدند. در Android 8.0 و بالاتر، این توابع اکنون در فضای نام android::hardware::hidl کپی می‌شوند یا به NDK منتقل می‌شوند. کد تولیدشده خودکار HIDL این توابع را به‌طور خودکار سریال‌سازی و سریال‌سازی معکوس می‌کند، بدون اینکه کد نوشته‌شده توسط کاربر در آن دخیل باشد.

مالکیت توصیف‌گر فایل و دسته

وقتی روش میانای HIDL را فراخوانی می‌کنید که شیء hidl_handle را ارسال (یا برمی‌گرداند) (چه در سطح بالا یا بخشی از نوع مرکب)، مالکیت توصیف‌گرهای فایل موجود در آن به‌صورت زیر است:

  • فراخواننده که شیء hidl_handle را به‌عنوان آرگومان ارسال می‌کند، مالکیت توصیف‌گرهای فایل موجود در native_handle_t را که دربرمی‌گیرد حفظ می‌کند؛ فراخواننده باید این توصیف‌گرهای فایل را پس‌از اتمام کار با آن‌ها ببندد.
  • فرایندی که شیء hidl_handle را برمی‌گرداند (با انتقال آن به تابع _cb)، مالکیت توصیف‌گرهای فایل موجود در native_handle_t پیچیده‌شده توسط شیء را حفظ می‌کند؛ فرایند باید این توصیف‌گرهای فایل را پس‌از اتمام کار با آن‌ها ببندد.
  • انتقالی که hidl_handle دریافت می‌کند مالکیت توصیفگرهای فایل درون native_handle_t پیچیده‌شده توسط شیء را دارد؛ گیرنده می‌تواند از این توصیفگرهای فایل در طول تماس برگشتی تراکنش به همین صورت استفاده کند، اما باید دستگیره بومی را برای استفاده از توصیفگرهای فایل فراتر از تماس برگشتی شبیه‌سازی کند. وقتی تراکنش انجام می‌شود، انتقال به‌طور خودکار close() را برای توصیفگرهای فایل فرا می‌خواند.

‫HIDL از دسته‌ها در جاوا پشتیبانی نمی‌کند (زیرا جاوا اصلاً از دسته‌ها پشتیبانی نمی‌کند).

آرایه‌های اندازه‌دار

برای آرایه‌های اندازه در ساختارهای HIDL، عناصر آن‌ها می‌توانند از هر نوعی باشند که یک ساختار می‌تواند شامل شود:

struct foo {
uint32_t[3] x; // array is contained in foo
};

رشته‌ها

رشته‌ها در C++ و Java متفاوت ظاهر می‌شوند، اما نوع ذخیره‌سازی انتقال زیربنایی یک ساختار C++ است. برای جزئیات، به انواع داده HIDL C++‎ یا انواع داده HIDL Java‎ مراجعه کنید.

توجه: انتقال رشته به یا از Java ازطریق میانای HIDL (ازجمله Java به Java) باعث تبدیل مجموعه نویسه‌ها می‌شود که ممکن است کدبندی اصلی را حفظ نکند.

الگوی نوع vec<T>

الگوی vec<T> نشان‌دهنده میان‌گیری با اندازه متغیر است که حاوی نمونه‌هایی از T است.

‫T می‌تواند یکی از موارد زیر باشد:

  • انواع اولیه (برای نمونه uint32_t)
  • رشته‌ها
  • تعداد شمارش‌های تعریف‌شده توسط کاربر
  • ساختارهای تعریف‌شده توسط کاربر
  • میانه‌ها، یا کلیدواژه interface (vec<IFoo>، vec<interface> فقط به‌عنوان پارامتر سطح بالا پشتیبانی می‌شود)
  • نام‌های کاربری
  • bitfield<U>
  • vec<U>، که U در این فهرست است به‌جز میانجی (مثلاً vec<vec<IFoo>> پشتیبانی نمی‌شود)
  • U[] (آرایه اندازه U)، که در آن U در این فهرست به‌جز میانای کاربری است

انواع تعریف‌شده توسط کاربر

این بخش انواع تعریف‌شده توسط کاربر را شرح می‌دهد.

شمارشی

‫HIDL از شمارش‌های ناشناس پشتیبانی نمی‌کند. درغیراین‌صورت، شمارش‌ها در HIDL مشابه C++11 هستند:

enum name : type { enumerator , enumerator = constexpr , …  }

یک enum پایه برحسب یکی از انواع عدد صحیح در HIDL تعریف می‌شود. اگر برای اولین شمارشگر یک شمارش براساس نوع عدد صحیح مقداری مشخص نشده باشد، مقدار پیش‌فرض ۰ خواهد بود. اگر برای شمارنده بعدی مقداری مشخص نشده باشد، مقدار پیش‌فرض به مقدار قبلی به‌علاوه یک تغییر می‌کند. برای مثال:

// RED == 0
// BLUE == 4 (GREEN + 1)
enum Color : uint32_t { RED, GREEN = 3, BLUE }

یک enum همچنین می‌تواند از یک enum تعریف‌شده قبلی ارث‌بری کند. اگر مقدار اولین شمارشگر یک شمارش فرزند (در این مورد FullSpectrumColor) مشخص نشده باشد، به‌طور پیش‌فرض مقدار آخرین شمارشگر شمارشگر والد به‌علاوه یک درنظر گرفته می‌شود. برای مثال:

// ULTRAVIOLET == 5 (Color:BLUE + 1)
enum FullSpectrumColor : Color { ULTRAVIOLET }

هشدار: وراثت Enum به‌صورت معکوس از اکثر انواع دیگر وراثت کار می‌کند. مقدار شمارشی فرزند نمی‌تواند به‌عنوان مقدار شمارشی والد استفاده شود. دلیل این موضوع این است که شمارش فرزند مقادیر بیشتری نسبت‌به شمارش والد دارد. بااین‌حال، مقدار شمارشی والد را می‌توان با اطمینان به‌عنوان مقدار شمارشی فرزند استفاده کرد زیرا مقادیر شمارشی فرزند براساس تعریف مجموعه فرامقدار مقادیر شمارشی والد است. هنگام طراحی میاناهای خود این نکته را درنظر داشته باشید، زیرا این بدان معناست که انواع ارجاع‌دهنده به شمارش‌های والد نمی‌توانند در تکرارهای بعدی میانای شما به شمارش‌های فرزند ارجاع دهند.

مقادیر شمارشی با دستورگان دونقطه (نه دستورگان نقطه به‌عنوان انواع تودرتو) ارجاع داده می‌شوند. نحو Type:VALUE_NAME است. اگر مقدار در همان نوع شمارشی یا انواع فرزند ارجاع داده شود، نیازی به مشخص کردن نوع نیست. مثال:

enum Grayscale : uint32_t { BLACK = 0, WHITE = BLACK + 1 };
enum Color : Grayscale { RED = WHITE + 1 };
enum Unrelated : uint32_t { FOO = Color:RED + 1 };

از Android 10 به بعد، شمارشی‌ها دارای len مشخصه‌ای هستند که می‌توان در عبارت‌های ثابت استفاده کرد. MyEnum::len تعداد کل ورودی‌ها در آن شمارش است. این مقدار با تعداد کل مقادیر متفاوت است، که ممکن است درصورت تکراری بودن مقادیر کمتر باشد.

Struct

‫HIDL از ساختارهای ناشناس پشتیبانی نمی‌کند. درغیراین‌صورت، ساختارها در HIDL بسیار شبیه به C هستند.

‫HIDL از ساختارهای داده با طول متغیر که کاملاً در یک ساختار قرار دارند پشتیبانی نمی‌کند. This includes the indefinite-length array that is sometimes used as the last field of a struct in C/C++ (sometimes seen with a size of [0]). HIDL vec<T> represents dynamically-sized arrays with the data stored in a separate buffer; such instances are represented with an instance of the vec<T> in the struct.

به‌همین ترتیب، string می‌تواند در struct قرار بگیرد (میان‌گیرهای مرتبط جدا هستند). در C++ تولیدشده، نمونه‌های نوع دسته HIDL ازطریق اشاره‌گری به دسته بومی واقعی نشان داده می‌شوند، زیرا نمونه‌های نوع داده زیرین طول متغیر دارند.

Union

‫HIDL از اتحادیه‌های ناشناس پشتیبانی نمی‌کند. درغیراین‌صورت، اتحادیه‌ها مشابه C هستند.

اتحادیه‌ها نمی‌توانند حاوی انواع اصلاح (مثل اشاره‌گرها، توصیف‌گرهای فایل، اشیای پیونددهنده) باشند. آن‌ها به فیلدهای خاص یا انواع مرتبط نیاز ندارند و به‌سادگی بااستفاده از memcpy() یا معادل آن کپی می‌شوند. یک union ممکن است مستقیماً حاوی چیزی نباشد (یا بااستفاده از ساختارهای داده دیگر حاوی چیزی نباشد) که نیاز به تنظیم افست‌های binder داشته باشد (یعنی ارجاع‌های رابط binder یا دسته). برای مثال:

union UnionType {
uint32_t a;
//  vec<uint32_t> r;  // Error: can't contain a vec<T>
uint8_t b;1
};
fun8(UnionType info); // Legal

اتحادیه‌ها را می‌توان در داخل ساختارها نیز تعریف کرد. برای مثال:

struct MyStruct {
    union MyUnion {
      uint32_t a;
      uint8_t b;
    }; // declares type but not member

    union MyUnion2 {
      uint32_t a;
      uint8_t b;
    } data; // declares type but not member
  }