تقوم إعلانات بيانات 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. لاستخدام الذاكرة المشتركة:
- احصل على مثيل
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<>
.