Типы данных

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

Форвардные декларации

В 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 . В противном случае у 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);

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

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