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

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

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

  • تستخدم structs بناء جملة C++؛ تدعم unions بناء جملة C++ افتراضيًا. يجب تسمية كليهما؛ لا يتم دعم الهياكل والنقابات المجهولة.
  • يُسمح باستخدام Typedefs في 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 المكون من تخطيط قياسي (مجموعة فرعية من متطلبات أنواع البيانات القديمة البسيطة) له تخطيط ذاكرة ثابت في كود C++ الذي تم إنشاؤه، ويتم فرضه باستخدام سمات محاذاة صريحة على أعضاء struct union .

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

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

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

الشروح

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

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

التصريحات إلى الأمام

في 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> . لا يمكن أن يكونوا أعضاء في vecs أو بنيات أو صفائف أو اتحادات متداخلة.

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 عبارة عن تعداد محدد من قبل المستخدم يقترح أن القيمة هي bitwise-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 تأخذ قيمة bitwise-OR للعلامة (أي يعرف أن استدعاء setFlags بالرقم 16 غير صالح). بدون bitfield ، يتم نقل هذه المعلومات فقط عبر التوثيق. بالإضافة إلى ذلك، يمكن لـ VTS التحقق فعليًا مما إذا كانت قيمة العلامات هي قيمة bitwise-OR لـ Flag.

التعامل مع النوع البدائي

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

دلالات HIDL هي نسخة حسب القيمة، مما يعني أنه يتم نسخ المعلمات. يتم التعامل مع أي أجزاء كبيرة من البيانات، أو البيانات التي تحتاج إلى المشاركة بين العمليات (مثل سياج المزامنة)، عن طريق تمرير واصفات الملفات التي تشير إلى كائنات ثابتة: ashmem للذاكرة المشتركة، أو الملفات الفعلية، أو أي شيء آخر يمكن أن يختبئ خلفه. واصف الملف. يقوم برنامج تشغيل الموثق بتكرار واصف الملف في العملية الأخرى.

original_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;

المقبض الأصلي عبارة عن مجموعة من ints وواصفات الملفات التي يتم تمريرها حسب القيمة. يمكن تخزين واصف ملف واحد في مقبض أصلي بدون إدخالات واصف ملف واحد. تمرير المقابض باستخدام المقابض الأصلية المغلفة بالنوع البدائي handle يضمن تضمين المقابض الأصلية مباشرة في HIDL.

نظرًا لأن native_handle_t له حجم متغير، فلا يمكن تضمينه مباشرة في البنية. يقوم حقل المقبض بإنشاء مؤشر إلى native_handle_t المخصص بشكل منفصل.

في الإصدارات السابقة من Android، تم إنشاء المقابض الأصلية باستخدام نفس الوظائف الموجودة في libcutils . في Android 8.0 والإصدارات الأحدث، يتم الآن نسخ هذه الوظائف إلى مساحة الاسم 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> مدعومة فقط كمعلمة ذات مستوى أعلى)
  • مقابض
  • حقل البت<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 عبر مؤشر إلى المقبض الأصلي الفعلي حيث أن مثيلات نوع البيانات الأساسي متغيرة الطول.

اتحاد

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
  }