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

تُنشئ بيانات 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

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

تعداد

يصبح التعداد في 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. إذا كانت القيمة تظهر في قائمة أبجدية أبجدية متعدّدة المرات، تظهر القيمة في النطاق عدّة مرات.

bitfield<T>

bitfield<T> (حيث يكون T تعدادًا محدّدًا من قِبل المستخدم) يصبح النوع الأساسي لهذا التعداد في C++. في المثال أعلاه، يصبح bitfield<Mode> هو uint8_t.

vec<T>

نموذج فئة hidl_vec<T> هو جزء من libhidlbase ويمكن استخدامه لتمرير متجه من أي نوع HIDL بحجم عشوائي. الحاوية ذات الحجم الثابت المماثلة هي hidl_array. يمكن أيضًا تهيئة hidl_vec<T> للإشارة إلى وحدة تخزين مؤقتة للبيانات الخارجية من النوع T باستخدام دالة hidl_vec::setToExternal().

بالإضافة إلى إنشاء/إدراج البنية بشكلٍ مناسب في ملف vec<T> الرأس الذي تم إنشاؤه، يؤدي استخدام vec<T> إلى إنشاء بعض 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) في methods التي تأخذ سلسلة HIDL.

struct

لا يمكن أن يحتوي 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 و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();

واجهة

يمكن تمرير الواجهات كعناصر. يمكن استخدام كلمة واجهة كسكر نحوي للنوع android.hidl.base@1.0::IBase، بالإضافة إلى ذلك، يتم تعريف الواجهة الحالية وأي واجهات مستورَدة كنوع.

يجب أن تكون المتغيّرات التي تحتوي على واجهات مؤشرات قوية: sp<IName>. دوال HIDL التي تأخذ مَعلمات الواجهة تحوِّل المؤشرات الأوّلية إلى مؤشرات قوية، ما يؤدي إلى سلوك غير واضح (يمكن محو المؤشر بشكل غير متوقَّع). لتجنُّب حدوث مشاكل، يجب دائمًا تخزين واجهات HIDL بتنسيق sp<>.