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

يوضِّح هذا القسم أنواع بيانات HIDL. للحصول على تفاصيل التنفيذ، اطّلِع على HIDL C++ (لعمليات تنفيذ C++ ) أو HIDL Java (لعمليات تنفيذ Java).

تشمل أوجه التشابه مع C++ ما يلي:

  • structs استخدام بنية C++‎، unions تتيح بنية C++‎ تلقائيًا يجب أن يكون كلاهما مُسمّىً، ولا يُسمح باستخدام البنى والاتحادات المجهولة.
  • يُسمح باستخدام تعريفات النوع في HIDL (كما هو الحال في C++).
  • يُسمح بالتعليقات بأسلوب C++ ويتم نسخها إلى ملف الرأس الذي تم إنشاؤه.

تشمل أوجه التشابه مع Java ما يلي:

  • تحدِّد HIDL مساحة اسم على غرار Java لكل ملف، ويجب أن تبدأ بعلامة android.hardware.. مساحة الاسم التي تم إنشاؤها بلغة C++ هي ::android::hardware::….
  • يتم تضمين جميع تعريفات الملف ضمن ملف مغلف interface بأسلوب Java.
  • تتبع نماذج تعريف صفائف HIDL أسلوب Java، وليس أسلوب C++. مثال:
    struct Point {
        int32_t x;
        int32_t y;
    };
    Point[3] triangle;   // sized array
  • تتشابه التعليقات مع تنسيق javadoc.

تمثيل البيانات

إنّ struct أو union المكوّنَين من Standard-Layout (مجموعة فرعية من متطلبات أنواع البيانات العادية القديمة) يتضمّنان تنسيقًا متسقًا لشدَّة كثافة ذاكرة في رمز C++ الذي تم إنشاؤه، ويتم فرضه باستخدام سمات محاذاة صريحة على عضوَي struct وunion.

يتم ربط أنواع HIDL الأساسية، بالإضافة إلى أنواع enum وbitfield (التي يتم اشتقاقها دائمًا من الأنواع الأساسية)، بأنواع C++ العادية مثل std::uint32_t من cstdint.

بما أنّ Java لا تتوافق مع الأنواع غير الموقَّعة، يتم ربط أنواع HIDL غير الموقَّعة بنوع Java الموقَّع المقابل. يتم ربط البنى بفئات Java، ويُربط المصفوفات بمصفوفات Java، ولا تتوفّر الاتحادات حاليًا في Java. يتم تخزين السلاسل داخليًا بترميز UTF8. بما أنّ Java تتيح استخدام سلاسل UTF16 فقط، تتم ترجمة قيم السلاسل المُرسَلة إلى أو من عملية تنفيذ Java، وقد لا تكون متطابقة عند إعادة الترجمة لأنّ مجموعات الأحرف لا تتم ربطها دائمًا بسلاسة.

يتم وضع علامة const على البيانات التي يتم استلامها عبر واجهة برمجة التطبيقات في C++، وهي في ذاكرة للقراءة فقط لا تستمر إلا طوال مدة طلب الدالة. سبق أن تم نسخ البيانات المستلَمة عبر واجهة برمجة التطبيقات في Java إلى عناصر Java، لذا يمكن الاحتفاظ بها بدون إجراء عمليات نسخ إضافية (ويمكن تعديلها).

التعليقات التوضيحية

يمكن إضافة تعليقات توضيحية بأسلوب Java إلى تعريفات الأنواع. تتم معالجة التعليقات التوضيحية من خلال الخلفية الخاصة بمجموعة اختبارات المورّد (VTS) في أداة تجميع HIDL، ولكن لا يفهم أداة تجميع HIDL أيًا من هذه التعليقات التوضيحية التي تمت معالجتها. بدلاً من ذلك، تتم معالجة التعليقات التوضيحية التي تم تحليلها في جدول VTS بواسطة "مُجمِّع جدول VTS" (VTSC).

تستخدِم التعليقات التوضيحية بنية Java: @annotation أو @annotation(value) أو @annotation(id=value, id=value…) حيث يمكن أن تكون القيمة عبارة عن تعبير ثابت أو سلسلة أو قائمة بالقيم داخل {}، تمامًا كما هو الحال في Java. يمكن إرفاق تعليقات توضيحية متعددة بالاسم نفسه بالعنصر نفسه.

نماذج البيان المُعاد توجيهها

في HIDL، قد لا يتمّ الإعلان عن البنى بشكل مُسبَق، ما يجعل أنواع البيانات المُحدَّدة من المستخدِم والتي تشير إلى نفسها مستحيلة (على سبيل المثال، لا يمكنك وصف قائمة مرتبطة أو شجرة في HIDL). إنّ معظم واجهات HAL الحالية (الإصدارات الأقدم من Android 8.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.
  • ويمكن استخدامه كنوع خاص في حقول البنية/الاتحاد، ومَعلمات الطريقة، والقيم المعروضة. ويُنظر إليها على أنّها واجهة عامة ومرادفة لمحاولة android.hidl.base@1.0::IBase.

على سبيل المثال، يحتوي IServiceManager على الطريقة التالية:

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

تهدف الطريقة إلى البحث عن بعض الواجهات بالاسم. وهو أيضًا مطابق لاستبدال الواجهة بـ android.hidl.base@1.0::IBase.

لا يمكن تمرير الواجهات إلا بطريقتَين: كمَعلمات من المستوى الأعلى أو كعضو في vec<IMyInterface>. ولا يمكن أن تكون عناصرًا في مصفوفات أو هياكل أو مجمعات أو مصفوفات مُدمجة.

MQDescriptorSync وMQDescriptorUnsync

يُرسِل نوعا MQDescriptorSync وMQDescriptorUnsync وصفتَي "قائمة الرسائل السريعة" (FMQ) المتزامنة أو غير المتزامنة عبر واجهة HIDL. لمعرفة التفاصيل، يُرجى الاطّلاع على HIDL C++ (لا يمكن استخدام طوابير FMQ في Java).

نوع الذاكرة

يُستخدَم النوع memory لتمثيل الذاكرة المشتركة غير المُحدَّدة في HIDL. لا يمكن استخدامها إلا في C++. ويمكن استخدام قيمة من هذا النوع على الطرف المستلِم لتهيئة عنصر IMemory، وربط الذاكرة وجعلها قابلة للاستخدام. لمعرفة التفاصيل، يُرجى الاطّلاع على HIDL C++.

تحذير: يجب أن تكون البيانات المنظَّمة التي يتم وضعها في ملف مشاركة الذاكرة من النوع الذي لا يتغيّر تنسيقه أبدًا طوال فترة استخدام إصدار الواجهة الذي يجتاز memory. بخلاف ذلك، يمكن أن تواجه HALs مشاكل توافق خطيرة.

نوع المؤشر

يُستخدم نوع 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);

يتعامل المُجمِّع مع نوع Flags بالطريقة نفسها التي يتعامل بها مع uint8_t.

لماذا لا يتم استخدام (u)int8_t/(u)int16_t/(u)int32_t/(u)int64_t؟ يقدّم استخدام bitfield معلومات HAL إضافية للقارئ، الذي يعرف الآن أنّ setFlags يأخذ قيمة OR على مستوى الوحدات بتية للإشارة (أي يعرف أنّ استدعاء setFlags باستخدام 16 غير صالح). بدون 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. في الإصدار 8.0 من نظام Android والإصدارات الأحدث، يتم الآن نسخ هذه الدوال إلى مساحة الاسم 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 مع المعرّفات في Java (لأنّ Java لا تتوافق مع المعرّفات على الإطلاق).

المصفوفات ذات الحجم المحدّد

بالنسبة إلى المصفوفات ذات الحجم في بنى 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 ,   }

يتم تعريف قائمة الأرقام الأساسية استنادًا إلى أحد أنواع الأعداد الصحيحة في HIDL. إذا لم يتم تحديد قيمة للمُعدِّد الأوّل لقائمة القيم المحدَّدة استنادًا إلى نوع عدد صحيح، تكون القيمة التلقائية هي 0. إذا لم يتم تحديد قيمة لعدّاد لاحق، تكون القيمة التلقائية هي القيمة السابقة بالإضافة إلى واحد. مثلاً:

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

يمكن أيضًا أن يكتسب متغير الإدراج الثابت قيمة من متغير إدراج ثابت تم تحديده سابقًا. إذا لم يتم تحديد قيمة لأوّل معرّف للعناصر في قائمة فرعية (في هذه الحالة FullSpectrumColor)، يتم ضبط القيمة التلقائية على قيمة معرّف العناصر الأول في القائمة الرئيسية بالإضافة إلى واحد. مثلاً:

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

تحذير: تعمل عملية اكتساب القيمة من العنصر النائب بشكل عكسي مقارنةً بمعظم أنواع اكتساب القيمة الأخرى. لا يمكن استخدام قيمة قائمة بقيم ثابتة فرعية كقيمة قائمة بقيم ثابتة رئيسية. ويعود السبب في ذلك إلى أنّ قائمة القيم المحدَّدة للعنصر الفرعي تتضمّن قيمًا أكثر من العنصر الأساسي. ومع ذلك، يمكن استخدام قيمة قائمة بقيم ثابتة رئيسية بأمان كقيمة قائمة بقيم ثابتة فرعية لأنّ قيم القوائم بقيم ثابتة الفرعية هي مجموعة فائقة من قيم القوائم بقيم ثابتة الرئيسية بحكم التعريف. ضع ذلك في الاعتبار عند تصميم الواجهات، لأنّ ذلك يعني أنّ الأنواع التي تشير إلى ملفّات التعريف المتعدّدة القيمة الخاصة بالعناصر الرئيسية لا يمكنها الإشارة إلى ملفّات التعريف المتعدّدة القيمة الخاصة بالعناصر الفرعية في النُسخ اللاحقة من واجهتك.

تتم الإشارة إلى قيم التعدادات باستخدام بنية النقطتَين (وليس بنية النقطة لأنّها أنواع مُدمجة). بناء الجملة هو 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 هو إجمالي عدد الإدخالات في هذا التعداد. ويختلف ذلك عن إجمالي عدد القيم، الذي قد يكون أصغر عند تكرار القيم.

بنية

لا تتيح HIDL استخدام البنى المجهولة. بخلاف ذلك، تشبه البنى في HIDL بنية C بشكلٍ كبير.

لا تتيح HIDL استخدام بنى البيانات ذات الطول المتغير والمضمّنة بالكامل في ملف تعريف بنية. ويشمل ذلك الصفيف الذي لا يحدّد طوله والذي يُستخدَم أحيانًا كهيّة الحقل الأخير في البنية في C/C++ (يظهر أحيانًا بحجم [0]). يمثّل HIDL vec<T> صفائفًا بحجم ديناميكي مع تخزين البيانات في مخزن ذاكرة مؤقت منفصل. ويتم تمثيل هذه المثيلات باستخدام مثيل vec<T> في struct.

وبالمثل، يمكن أن يتضمّن string struct (تكون وحدات التخزين المؤقت المرتبطة منفصلة). في رمز C++ الذي تم إنشاؤه، يتم تمثيل نُسخ HIDL من نوع المعرّف من خلال مؤشر إلى المعرّف الأصلي الفعلي لأنّ نُسخ نوع البيانات الأساسي تكون متغيرة الطول.

Union

لا تتوافق HIDL مع المجموعات المجهولة. بخلاف ذلك، تكون عمليات الدمج مشابهة لتلك في C.

لا يمكن أن تحتوي العناصر المُدمَجة على أنواع إصلاح (مثل المؤشرات ووصاف الملفات وعناصر الربط ). ولا تحتاج إلى حقول خاصة أو أنواع مرتبطة، ويتم نسخها ببساطة باستخدام memcpy() أو ما يعادله. قد لا يحتوي الائتلاف مباشرةً على أيّ شيء يتطلّب ضبط إزاحات الربط (أي المعرّف أو مراجع واجهة الربط) (أو يحتوي باستخدام هياكل بيانات أخرى). مثلاً:

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
  }