أنواع البيانات

تقوم إعلانات بيانات HIDL بإنشاء بنيات بيانات ذات تخطيط قياسي لـ C++. يمكن وضع هذه الهياكل في أي مكان يبدو طبيعيًا (على المكدس، في الملف أو النطاق العام، أو على الكومة) ويمكن تكوينها بنفس الطريقة. يستدعي رمز العميل رمز وكيل HIDL الذي يمرر مراجع const والأنواع البدائية، بينما يخفي رمز الوكيل وكعب الروتين تفاصيل التسلسل.

ملاحظة: لا يلزم في أي وقت وجود تعليمات برمجية مكتوبة من قبل المطور لإجراء تسلسل أو إلغاء تسلسل بنيات البيانات بشكل صريح.

يقوم الجدول أدناه بتعيين أساسيات 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

تصف الأقسام أدناه أنواع البيانات بمزيد من التفاصيل.

التعداد

يصبح التعداد في HIDL تعدادًا في 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، يمكن تكرار التعداد باستخدام ::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 . إذا ظهرت قيمة في التعداد عدة مرات، فستظهر القيمة في النطاق عدة مرات.

حقل البت<T>

يصبح bitfield<T> (حيث T عبارة عن تعداد محدد من قبل المستخدم) هو النوع الأساسي لهذا التعداد في 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 بواسطة فئة 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= ، والتحويلات الضمنية، والدالة .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++، وهي عبارة عن غلاف بسيط حول مؤشر إلى كائن 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 واصفات الملفات المرفقة ما يلي:

  • بعد استدعاء الأسلوب 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<> .