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 nonative_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 nonative_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 donative_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 automaticamenteclose()
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 }