تُنشئ بيانات 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. لاستخدام
الذاكرة المشتركة:
- احصل على مثيل من
IAllocator
(حاليًا، لا يتوفّر سوى مثيل "ashmem") واستخدِمه لتخصيص ذاكرة مشترَكة. - تُعرِض دالة
IAllocator::allocate()
عنصرhidl_memory
يمكن تمريره من خلال HIDL RPC وربطه بعملية باستخدام دالةmapMemory
فيlibhidlmemory
. - تعرض
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<>
.