Tipos de dados

Esta seção descreve os tipos de dados HIDL. Para detalhes de implementação, consulte HIDL C++ (para implementações em C++) ou HIDL Java (para implementações em Java).

As semelhanças com o C++ incluem:

  • structs usa a sintaxe C++. unions oferece suporte à sintaxe C++ por padrão. Ambos precisam ser nomeados. Não há suporte para structs e uniões anônimas.
  • Os Typedef são permitidos no HIDL (como são em C++).
  • Comentários no estilo C++ são permitidos e copiados para o arquivo de cabeçalho gerado.

As semelhanças com o Java incluem:

  • Para cada arquivo, o HIDL define um namespace no estilo Java que precisa começar com android.hardware.. O namespace C++ gerado é ::android::hardware::….
  • Todas as definições do arquivo estão contidas em um wrapper interface no estilo Java.
  • As declarações de matriz HIDL seguem o estilo Java, não o estilo C++. Exemplo:
    struct Point {
        int32_t x;
        int32_t y;
    };
    Point[3] triangle;   // sized array
  • Os comentários são semelhantes ao formato javadoc.

Representação de dados

Um struct ou union composto por layout padrão (um subconjunto do requisito de tipos de dados simples antigos) tem um layout de memória consistente no código C++ gerado, aplicado com atributos de alinhamento explícito em membros struct e union.

Os tipos primitivos de HIDL, bem como os tipos enum e bitfield (que sempre derivam de tipos primitivos), são mapeados para tipos C++ padrão, como std::uint32_t de cstdint.

Como o Java não oferece suporte a tipos não assinados, os tipos HIDL não assinados são mapeados para o tipo Java assinado correspondente. Structs são mapeadas para classes Java, matrizes são mapeadas para matrizes Java e uniões não são compatíveis com Java. As strings são armazenadas internamente como UTF8. Como o Java oferece suporte apenas a strings UTF16, os valores de string enviados para ou de uma implementação Java são traduzidos e podem não ser idênticos na nova tradução, já que os conjuntos de caracteres nem sempre são mapeados corretamente.

Os dados recebidos por IPC em C++ são marcados como const e ficam em memória somente leitura que persiste apenas durante a duração da chamada de função. Os dados recebidos por IPC no Java já foram copiados para objetos Java, portanto, podem ser mantidos sem cópias adicionais (e podem ser modificados).

Anotações

Anotações no estilo Java podem ser adicionadas a declarações de tipo. As anotações são analisadas pelo back-end do conjunto de testes de fornecedor (VTS, na sigla em inglês) do compilador HIDL, mas nenhuma dessas anotações analisadas é realmente entendida pelo compilador HIDL. Em vez disso, as anotações VTS analisadas são processadas pelo VTS Compiler (VTSC).

As anotações usam a sintaxe Java: @annotation ou @annotation(value) ou @annotation(id=value, id=value…), em que o valor pode ser uma expressão constante, uma string ou uma lista de valores dentro de {}, assim como no Java. Várias anotações com o mesmo nome podem ser anexadas ao mesmo item.

Declarações de encaminhamento

Em HIDL, as estruturas podem não ser declaradas de forma antecipada, o que torna impossíveis os tipos de dados auto-referenciais definidos pelo usuário. Por exemplo, não é possível descrever uma lista vinculada ou uma árvore em HIDL. A maioria dos HALs existentes (antes do Android 8.x) tem uso limitado de declarações forward, que podem ser removidas reorganizando as declarações de estrutura de dados.

Essa restrição permite que as estruturas de dados sejam copiadas por valor com uma cópia profunda simples, em vez de manter o controle de valores de ponteiro que podem ocorrer várias vezes em uma estrutura de dados auto-referenciada. Se os mesmos dados forem transmitidos duas vezes, como com dois parâmetros de método ou vec<T>s que apontam para os mesmos dados, duas cópias separadas serão feitas e enviadas.

Declarações aninhadas

O HIDL oferece suporte a declarações aninhadas para quantos níveis forem necessários (com uma exceção abaixo). Exemplo:

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
    }
    

A exceção é que os tipos de interface só podem ser incorporados em vec<T> e apenas um nível de profundidade (sem vec<vec<IFoo>>).

Sintaxe de ponteiro bruto

A linguagem HIDL não usa * e não oferece suporte à flexibilidade completa de ponteiros brutos C/C++. Para saber como o HIDL encapsula ponteiros e matrizes/vetores, consulte o modelo vec<T>.

Interfaces

A palavra-chave interface tem dois usos.

  • Ele abre a definição de uma interface em um arquivo .hal.
  • Ele pode ser usado como um tipo especial em campos de struct/union, parâmetros de método e retornos. Ela é considerada uma interface geral e sinônimo de android.hidl.base@1.0::IBase.

Por exemplo, IServiceManager tem o seguinte método:

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

O método promete procurar uma interface por nome. Também é idêntico substituir a interface por android.hidl.base@1.0::IBase.

As interfaces só podem ser transmitidas de duas maneiras: como parâmetros de nível superior ou como membros de um vec<IMyInterface>. Eles não podem ser membros de vetores, structs, arrays ou uniões aninhados.

MQDescriptorSync e MQDescriptorUnsync

Os tipos MQDescriptorSync e MQDescriptorUnsync transmitem descritores de fila de mensagens rápidas (FMQ, na sigla em inglês) sincronizados ou não sincronizados em uma interface HIDL. Para mais detalhes, consulte C++ do HIDL (as FMQs não têm suporte no Java).

tipo de memória

O tipo memory é usado para representar a memória compartilhada não mapeada em HIDL. Ele tem suporte apenas no C++. Um valor desse tipo pode ser usado no lado receptor para inicializar um objeto IMemory, mapeando a memória e tornando-a utilizável. Para saber mais, consulte HIDL C++.

Aviso:os dados estruturados colocados na memória compartilhada precisam ser um tipo cujo formato nunca muda durante a vida útil da versão da interface que transmite o memory. Caso contrário, os HALs podem sofrer problemas de compatibilidade fatais.

tipo de ponteiro

O tipo pointer é apenas para uso interno do HIDL.

Modelo de tipo bitfield<T>

bitfield<T>, em que T é uma enumeração definida pelo usuário, sugere que o valor é uma operação OR bit a bit dos valores de enumeração definidos em T. No código gerado, bitfield<T> aparece como o tipo de T. Por exemplo:

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

O compilador processa o tipo Flags da mesma forma que uint8_t.

Por que não usar (u)int8_t/(u)int16_t/(u)int32_t/(u)int64_t? O uso de bitfield fornece informações adicionais do HAL para o leitor, que agora sabe que setFlags recebe um valor bitwise-OR de flag (ou seja, sabe que chamar setFlags com 16 é inválido). Sem bitfield, essas informações são transmitidas apenas pela documentação. Além disso, o VTS pode verificar se o valor das flags é um OR de bits da flag.

Identificadores de tipo primitivo

AVISO:endereços de qualquer tipo (até mesmo endereços de dispositivos físicos) nunca podem fazer parte de um identificador nativo. Transmitir essas informações entre processos é perigoso e os torna suscetíveis a ataques. Todos os valores transmitidos entre processos precisam ser validados antes de serem usados para procurar a memória alocada em um processo. Caso contrário, identificadores incorretos podem causar acesso incorreto à memória ou corrupção da memória.

A semântica do HIDL é de cópia por valor, o que implica que os parâmetros são copiados. Qualquer grande quantidade de dados ou dados que precisam ser compartilhados entre processos (como uma cerca de sincronização) são processados transmitindo descritores de arquivo que apontam para objetos persistentes: ashmem para memória compartilhada, arquivos reais ou qualquer outra coisa que possa se esconder atrás de um descritor de arquivo. O driver de vinculação duplica o descritor de arquivo no outro processo.

native_handle_t

O Android oferece suporte a native_handle_t, um conceito geral de handle definido em 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;

Um identificador nativo é uma coleção de ints e descritores de arquivos que são transmitidos por valor. Um único descritor de arquivo pode ser armazenado em um identificador nativo sem ints e um único descritor de arquivo. A transmissão de identificadores usando identificadores nativos encapsulados com o tipo primitivo handle garante que os identificadores nativos sejam incluídos diretamente no HIDL.

Como um native_handle_t tem tamanho variável, ele não pode ser incluído diretamente em um struct. Um campo de identificador gera um ponteiro para um native_handle_t alocado separadamente.

Em versões anteriores do Android, os identificadores nativos eram criados usando as mesmas funções presentes na libcutils. No Android 8.0 e versões mais recentes, essas funções agora são copiadas para o namespace android::hardware::hidl ou movidas para o NDK. O código gerado automaticamente pelo HIDL serializa e deserializa essas funções automaticamente, sem o envolvimento do código escrito pelo usuário.

Processar e gerenciar a propriedade do descritor de arquivo

Quando você chama um método de interface HIDL que transmite (ou retorna) um objeto hidl_handle (de nível superior ou parte de um tipo composto), a propriedade dos descritores de arquivo contidos nele é a seguinte:

  • O autor da chamada que transmite um objeto hidl_handle como argumento mantém a propriedade dos descritores de arquivos contidos no native_handle_t que ele envolve. O autor da chamada precisa fechar esses descritores de arquivos quando terminar de usá-los.
  • O processo que retorna um objeto hidl_handle (transmitindo-o para uma função _cb) mantém a propriedade dos descritores de arquivos contidos no native_handle_t envolvido pelo objeto. O processo precisa fechar esses descritores de arquivos quando terminar de usá-los.
  • Um transporte que recebe um hidl_handle tem propriedade dos descritores de arquivo dentro do native_handle_t encapsulado pelo objeto. O receptor pode usar esses descritores de arquivo como estão durante o callback da transação, mas precisa clonar o identificador nativo para usar os descritores de arquivo além do callback. O transporte chama automaticamente close() para os descritores de arquivo quando a transação é concluída.

O HIDL não oferece suporte a identificadores em Java, porque o Java não oferece suporte a identificadores.

Matrizes dimensionadas

Para matrizes dimensionadas em structs HIDL, os elementos podem ser de qualquer tipo que um struct pode conter:

struct foo {
uint32_t[3] x; // array is contained in foo
};

Strings

As strings aparecem de maneira diferente em C++ e Java, mas o tipo de armazenamento de transporte subjacente é uma estrutura C++. Para mais detalhes, consulte Tipos de dados C++ do HIDL ou Tipos de dados Java do HIDL.

Observação:transmitir uma string para ou do Java por uma interface HIDL (incluindo Java para Java) causa conversões de conjunto de caracteres que podem não preservar a codificação original.

Modelo de tipo vec<T>

O modelo vec<T> representa um buffer de tamanho variável que contém instâncias de T.

T pode ser:

  • Tipos primitivos (por exemplo, uint32_t)
  • Strings
  • Tipos enumerados definidos pelo usuário
  • Estruturas definidas pelo usuário
  • Interfaces ou a palavra-chave interface (vec<IFoo>, vec<interface> é aceito apenas como um parâmetro de nível superior)
  • Identificadores
  • bitfield<U>
  • vec<U>, em que U está nesta lista, exceto a interface (por exemplo, vec<vec<IFoo>> não tem suporte)
  • U[] (matriz dimensionada de U), em que U está nesta lista, exceto a interface

Tipos definidos pelo usuário

Esta seção descreve os tipos definidos pelo usuário.

Tipo enumerado

O HIDL não oferece suporte a enumerações anônimas. Caso contrário, os tipos enumerados em HIDL são semelhantes ao C++11:

enum name : type { enumerator , enumerator = constexpr ,   }

Um tipo enumerado básico é definido em termos de um dos tipos de números inteiros no HIDL. Se nenhum valor for especificado para o primeiro enumerador de uma enumeração baseada em um tipo inteiro, o valor padrão será 0. Se nenhum valor for especificado para um enumerador posterior, o valor padrão será o valor anterior mais um. Exemplo:

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

Um tipo enumerado também pode herdar de um tipo enumerado definido anteriormente. Se nenhum valor for especificado para o primeiro enumerador de um tipo enumerado filho (neste caso, FullSpectrumColor), o padrão será o valor do último enumerador do tipo enumerado pai mais um. Exemplo:

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

Aviso:a herança de tipo enumerado funciona de trás para frente na maioria dos outros tipos de herança. Um valor de tipo enumerado filho não pode ser usado como um valor de tipo enumerado pai. Isso ocorre porque um tipo enumerado filho inclui mais valores do que o pai. No entanto, um valor de tipo enumerado pai pode ser usado com segurança como um valor de tipo enumerado filho, porque os valores de tipo enumerado filho são, por definição, um superconjunto de valores de tipo enumerado pai. Lembre-se disso ao projetar interfaces, porque isso significa que os tipos que se referem a enums pai não podem se referir a enums filhos em iterações posteriores da interface.

Os valores de enumerações são referidos com a sintaxe de dois-pontos (não a sintaxe de ponto como tipos aninhados). A sintaxe é Type:VALUE_NAME. Não é necessário especificar o tipo se o valor for referenciado no mesmo tipo de enumeração ou tipos filhos. Exemplo:

enum Grayscale : uint32_t { BLACK = 0, WHITE = BLACK + 1 };
enum Color : Grayscale { RED = WHITE + 1 };
enum Unrelated : uint32_t { FOO = Color:RED + 1 };

A partir do Android 10, os tipos enumerados têm um atributo len que pode ser usado em expressões constantes. MyEnum::len é o número total de entradas nessa enumeração. Isso é diferente do número total de valores, que pode ser menor quando os valores são duplicados.

Struct

O HIDL não oferece suporte a structs anônimos. Caso contrário, as estruturas em HIDL são muito semelhantes ao C.

O HIDL não oferece suporte a estruturas de dados de comprimento variável contidas totalmente em uma struct. Isso inclui a matriz de comprimento indefinido que às vezes é usada como o último campo de uma struct em C/C++ (às vezes com um tamanho de [0]). O vec<T> do HIDL representa matrizes de tamanho dinâmico com os dados armazenados em um buffer separado. Essas instâncias são representadas com uma instância do vec<T> no struct.

Da mesma forma, string pode ser contido em um struct (os buffers associados são separados). No C++ gerado, as instâncias do tipo de identificador HIDL são representadas por um ponteiro para o identificador nativo real, já que as instâncias do tipo de dados subjacente têm comprimento variável.

Union

O HIDL não oferece suporte a uniões anônimas. Caso contrário, as uniões são semelhantes ao C.

As uniões não podem conter tipos de correção, como ponteiros, descritores de arquivo e objetos de vinculação. Eles não precisam de campos especiais ou tipos associados e são simplesmente copiados usando memcpy() ou equivalente. Uma união pode não conter diretamente (ou conter usando outras estruturas de dados) nada que exija a definição de deslocamentos de vinculação (ou seja, referências de identificador ou de interface de vinculação). Exemplo:

union UnionType {
uint32_t a;
//  vec<uint32_t> r;  // Error: can't contain a< >vecT
uint8_t b;1
};
fun8(UnionType info); // Legal

As uniões também podem ser declaradas dentro de structs. Exemplo:

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
  }