डेटा टाइप

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

यहां दिए गए सेक्शन में, डेटा टाइप के बारे में ज़्यादा जानकारी दी गई है.

enum

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::setToExternal() फ़ंक्शन का इस्तेमाल करके, hidl_vec<T> को T टाइप के बाहरी डेटा बफ़र पर ले जाने के लिए भी शुरू किया जा सकता है.

जनरेट किए गए C++ हेडर में स्ट्रक्चर को सही तरीके से उत्सर्जित/शामिल करने के अलावा, vec<T> का इस्तेमाल करने से, std::vector और बेर T पॉइंटर में अनुवाद करने के लिए कुछ सुविधाजनक फ़ंक्शन जनरेट होते हैं. अगर vec<T> का इस्तेमाल पैरामीटर के तौर पर किया जाता है, तो उस पैरामीटर के लिए, HIDL स्ट्रक्चर और std::vector<T> टाइप, दोनों को स्वीकार करने और पास करने के लिए, फ़ंक्शन को ओवरलोड किया जाता है (दो प्रोटोटाइप जनरेट किए जाते हैं).

ऐरे

hidl में कॉन्सटेंट ऐरे को libhidlbase में hidl_array क्लास से दिखाया जाता है. hidl_array<T, S1, S2, …, SN>, N डाइमेंशन वाले तय साइज़ के ऐरे को दिखाता है T[S1][S2]…[SN].

स्ट्रिंग

hidl_string क्लास (libhidlbase का हिस्सा) का इस्तेमाल, HIDL इंटरफ़ेस पर स्ट्रिंग भेजने के लिए किया जा सकता है. इसकी जानकारी /system/libhidl/base/include/hidl/HidlSupport.h में दी गई है. क्लास में पहली स्टोरेज जगह, उसके वर्ण बफ़र का पॉइंटर होती है.

hidl_string, operator=, इंप्लिसिट कास्ट, और .c_str() फ़ंक्शन का इस्तेमाल करके, std::string and char* (C-स्टाइल स्ट्रिंग) में बदलने और उससे बदलने का तरीका जानता है. HIDL स्ट्रिंग स्ट्रक्चर में, कॉपी कंस्ट्रक्टर और असाइनमेंट ऑपरेटर होते हैं, ताकि:

  • std::string या C स्ट्रिंग से HIDL स्ट्रिंग लोड करें.
  • HIDL स्ट्रिंग से नया std::string बनाएं.

इसके अलावा, HIDL स्ट्रिंग में कन्वर्ज़न कन्स्ट्रक्टर होते हैं, ताकि C स्ट्रिंग (char *) और C++ स्ट्रिंग (std::string) का इस्तेमाल उन तरीकों पर किया जा सके जो HIDL स्ट्रिंग लेते हैं.

struct

HIDL में struct में सिर्फ़ तय साइज़ के डेटा टाइप हो सकते हैं और कोई फ़ंक्शन नहीं हो सकता. HIDL स्ट्रक्चर की परिभाषाएं, सी++ में सीधे स्टैंडर्ड-लेआउट struct पर मैप होती हैं. इससे यह पक्का होता है कि struct का मेमोरी लेआउट एक जैसा हो. किसी स्ट्रक्चर में HIDL टाइप शामिल हो सकते हैं. इनमें handle, string, और vec<T> जैसे टाइप शामिल हैं, जो अलग-अलग वैरिएबल-लेंथ बफ़र पर ले जाते हैं.

हैंडल

चेतावनी: किसी भी तरह के पते (यहां तक कि डिवाइस के पते भी) को कभी भी नेटिव हैंडल का हिस्सा नहीं बनाया जाना चाहिए. इस जानकारी को प्रोसेस के बीच भेजना खतरनाक है. इससे, प्रोसेस पर हमले की संभावना बढ़ जाती है. किसी प्रोसेस में, ऐलोकेट की गई मेमोरी को देखने के लिए, प्रोसेस के बीच भेजी गई किसी भी वैल्यू की पुष्टि की जानी चाहिए. ऐसा न करने पर, खराब हैंडल की वजह से मेमोरी का ऐक्सेस खराब हो सकता है या मेमोरी खराब हो सकती है.

handle टाइप को C++ में hidl_handle स्ट्रक्चर से दिखाया जाता है. यह 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 के पास, शामिल किए गए फ़ाइल डिस्क्रिप्टर का मालिकाना हक होने की ये स्थितियां हैं:

  • shouldOwn पैरामीटर को true पर सेट करके, setTo(native_handle_t* handle, bool shouldOwn) तरीके को कॉल करने के बाद
  • जब hidl_handle ऑब्जेक्ट को किसी दूसरे hidl_handle ऑब्जेक्ट से कॉपी करके बनाया गया हो
  • जब hidl_handle ऑब्जेक्ट को किसी दूसरे hidl_handle ऑब्जेक्ट से कॉपी-असाइन किया जाता है

hidl_handle, native_handle_t* ऑब्जेक्ट में/से इंप्लिसिट और एक्सप्लिसिट, दोनों तरह के कन्वर्ज़न उपलब्ध कराता है. HIDL में handle टाइप का मुख्य इस्तेमाल, HIDL इंटरफ़ेस पर फ़ाइल डिस्क्रिप्टर पास करने के लिए किया जाता है. इसलिए, किसी एक फ़ाइल के डिस्क्रिप्टर को native_handle_t से दिखाया जाता है. इसमें कोई int नहीं होता और एक fd होता है. अगर क्लाइंट और सर्वर अलग-अलग प्रोसेस में मौजूद हैं, तो आरपीसी लागू करने की सुविधा, फ़ाइल डिस्क्रिप्टर को अपने-आप मैनेज करती है. इससे यह पक्का होता है कि दोनों प्रोसेस एक ही फ़ाइल पर काम कर सकें.

किसी प्रोसेस से hidl_handle में मिलने वाला फ़ाइल डिस्क्रिप्टर, उस प्रोसेस में मान्य होता है. हालांकि, यह फ़ंक्शन मिलने के बाद नहीं रहता. फ़ंक्शन के बंद होने पर, यह बंद हो जाता है. अगर किसी प्रोसेस को फ़ाइल डिस्क्रिप्टर का ऐक्सेस हमेशा चाहिए, तो उसे dup() में शामिल फ़ाइल डिस्क्रिप्टर को hidl_handle ऑब्जेक्ट की पूरी कॉपी बनानी होगी.

यादें

HIDL memory टाइप, libhidlbase में hidl_memory क्लास पर मैप होता है. यह मैप, शेयर की गई ऐसी मेमोरी को दिखाता है जिसे मैप नहीं किया गया है. यह वह ऑब्जेक्ट है जिसे HIDL में मेमोरी शेयर करने के लिए, प्रोसेस के बीच पास किया जाना चाहिए. शेयर की गई मेमोरी का इस्तेमाल करने के लिए:

  1. IAllocator का कोई इंस्टेंस पाएं (फ़िलहाल सिर्फ़ इंस्टेंस "ashmem" उपलब्ध है) और शेयर की गई मेमोरी को ऐलोकेट करने के लिए उसका इस्तेमाल करें.
  2. IAllocator::allocate() एक hidl_memory ऑब्जेक्ट दिखाता है, जिसे HIDL RPC के ज़रिए पास किया जा सकता है और libhidlmemory के mapMemory फ़ंक्शन का इस्तेमाल करके किसी प्रोसेस में मैप किया जा सकता है.
  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<> के तौर पर सेव करें.