Типы данных

В этом разделе описываются типы данных 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 или из нее, переводятся и могут не совпадать при повторном переводе, поскольку наборы символов не всегда отображаются плавно.

Данные, полученные через IPC в C++, помечаются как const и находятся в памяти только для чтения, которая сохраняется только на время вызова функции. Данные, полученные через IPC в Java, уже скопированы в объекты Java, поэтому их можно сохранить без дополнительного копирования (и можно модифицировать).

Аннотации

Аннотации в стиле Java могут быть добавлены к объявлениям типов. Аннотации анализируются серверной частью Vendor Test Suite (VTS) компилятора HIDL, но ни одна из таких проанализированных аннотаций фактически не понимается компилятором HIDL. Вместо этого проанализированные аннотации VTS обрабатываются компилятором VTS (VTSC).

Аннотации используют синтаксис Java: @annotation или @annotation(value) или @annotation(id=value, id=value…) где value может быть постоянным выражением, строкой или списком значений внутри {} , как и в Ява. К одному элементу можно прикрепить несколько аннотаций с одним и тем же именем.

Форвардные объявления

В HIDL нельзя объявлять структуры заранее, что делает невозможным определение пользователем самореферентных типов данных (например, вы не можете описать связанный список или дерево в HIDL). Большинство существующих (до Android 8.x) HAL имеют ограниченное использование предварительных объявлений, которые можно удалить, переупорядочив объявления структуры данных.

Это ограничение позволяет копировать структуры данных по значению с помощью простого глубокого копирования вместо отслеживания значений указателя, которые могут встречаться несколько раз в самореферентной структуре данных. Если одни и те же данные передаются дважды, например, с двумя параметрами метода или 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 . В противном случае HAL могут столкнуться с фатальными проблемами совместимости.

тип указателя

Тип pointer предназначен только для внутреннего использования HIDL.

шаблон типа bitfield<T>

bitfield<T> , в котором T является определяемым пользователем перечислением , предполагает, что значение представляет собой побитовое ИЛИ значений перечисления, определенных в 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);

Компилятор обрабатывает флаги типа так же, как uint8_t .

Почему бы не использовать (u)int8_t / (u)int16_t / (u)int32_t / (u)int64_t ? Использование bitfield предоставляет дополнительную информацию HAL читателю, который теперь знает, что setFlags принимает значение Flag побитового ИЛИ (т. е. знает, что вызов setFlags с 16 недопустим). Без bitfield эта информация передается только через документацию. Кроме того, VTS может фактически проверить, является ли значение flags побитовым ИЛИ флага.

обрабатывать примитивный тип

ПРЕДУПРЕЖДЕНИЕ. Адреса любого типа (даже адреса физических устройств) никогда не должны быть частью собственного дескриптора. Передача этой информации между процессами опасна и делает их уязвимыми для атак. Любые значения, передаваемые между процессами, должны быть проверены, прежде чем они будут использоваться для поиска выделенной памяти внутри процесса. В противном случае плохие дескрипторы могут привести к неправильному доступу к памяти или повреждению памяти.

Семантика 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 . В 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 или из 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 }

Предупреждение. Наследование Enum работает в обратном направлении от большинства других типов наследования. Значение дочернего перечисления нельзя использовать в качестве значения родительского перечисления. Это связано с тем, что дочернее перечисление включает больше значений, чем родительское. Однако значение родительского перечисления можно безопасно использовать в качестве значения дочернего перечисления, поскольку значения дочернего перечисления по определению являются надмножеством значений родительского перечисления. Имейте это в виду при разработке интерфейсов, так как это означает, что типы, ссылающиеся на родительские перечисления, не могут ссылаться на дочерние перечисления в более поздних итерациях вашего интерфейса.

Значения перечислений упоминаются с синтаксисом двоеточия (а не синтаксисом точки как вложенные типы). Синтаксис: 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
  }