Tipos de dados

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

As semelhanças com C++ incluem:

  • structs usam sintaxe C++; os unions suportam a sintaxe C++ por padrão. Ambos devem ser nomeados; estruturas e uniões anônimas não são suportadas.
  • Typedefs são permitidos em HIDL (como em C++).
  • Comentários no estilo C++ são permitidos e copiados para o arquivo de cabeçalho gerado.

As semelhanças com Java incluem:

  • Para cada arquivo, o HIDL define um namespace no estilo Java que deve começar com android.hardware. . O namespace C++ gerado é ::android::hardware::… .
  • Todas as definições do arquivo estão contidas em um wrapper de interface no estilo Java.
  • As declarações de array 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

Uma struct ou union composta de Layout Padrão (um subconjunto do requisito de tipos de dados simples) tem um layout de memória consistente no código C++ gerado, imposto com atributos de alinhamento explícitos em membros de struct e union .

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

Como o Java não suporta tipos não assinados, os tipos HIDL não assinados são mapeados para o tipo Java assinado correspondente. Mapeamento de estruturas para classes Java; matrizes mapeiam para matrizes Java; as uniões não são suportadas atualmente em Java. Strings são armazenadas internamente como UTF8. Como o Java suporta apenas 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 retradução, pois os conjuntos de caracteres nem sempre são mapeados sem problemas.

Os dados recebidos por IPC em C++ são marcados como const e estão na memória somente leitura que persiste apenas durante a chamada da função. Os dados recebidos por IPC em Java já foram copiados em objetos Java, portanto, podem ser retidos sem cópia adicional (e podem ser modificados).

Anotações

Anotações no estilo Java podem ser adicionadas às declarações de tipo. As anotações são analisadas pelo backend Vendor Test Suite (VTS) do compilador HIDL, mas nenhuma dessas anotações analisadas é realmente compreendida pelo compilador HIDL. Em vez disso, as anotações VTS analisadas são tratadas pelo compilador VTS (VTSC).

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

Encaminhar declarações

Em HIDL, structs não podem ser declarados para frente, impossibilitando tipos de dados auto-referenciais definidos pelo usuário (por exemplo, você não pode descrever uma lista vinculada ou uma árvore em HIDL). A maioria das HALs existentes (pré-Android 8.x) tem uso limitado de declarações de encaminhamento, 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 acompanhar os valores de ponteiro que podem ocorrer várias vezes em uma estrutura de dados auto-referencial. Se os mesmos dados forem passados ​​duas vezes, como com dois parâmetros de método ou vec<T> s que apontam para os mesmos dados, duas cópias separadas são feitas e entregues.

Declarações aninhadas

HIDL suporta declarações aninhadas em quantos níveis forem desejados (com uma exceção indicada abaixo). Por 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 (no vec<vec<IFoo>> ).

Sintaxe de ponteiro bruto

A linguagem HIDL não usa * e não oferece suporte à flexibilidade total de ponteiros brutos C/C++. Para obter detalhes sobre como o HIDL encapsula ponteiros e matrizes/vetores, consulte vec<T> template .

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 struct/union, parâmetros de método e retornos. Ele é visto como 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 pesquisar alguma interface pelo nome. Também é idêntico para substituir interface com android.hidl.base@1.0::IBase .

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

MQDescriptorSync & MQDescriptorUnsync

Os tipos MQDescriptorSync e MQDescriptorUnsync transmitem descritores de Fast Message Queue (FMQ) sincronizados ou não sincronizados por meio de uma interface HIDL. Para obter detalhes, consulte HIDL C++ (FMQs não são suportados em Java).

tipo de memória

O tipo de memory é usado para representar a memória compartilhada não mapeada em HIDL. Ele é suportado apenas em C++. Um valor desse tipo pode ser usado na extremidade receptora para inicializar um objeto IMemory , mapeando a memória e tornando-a utilizável. Para obter detalhes, consulte HIDL C++ .

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

tipo de ponteiro

O tipo de pointer é apenas para uso interno HIDL.

modelo de tipo bitfield<T>

bitfield<T> em que T é uma enumeração definida pelo usuário sugere que o valor é um OR bit a bit dos valores de enumeração definidos em T . No código gerado, bitfield<T> aparece como o tipo subjacente 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 trata 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 HAL adicionais para o leitor, que agora sabe que setFlags recebe um valor OR bit a bit de Flag (ou seja, sabe que chamar setFlags com 16 é inválido). Sem bitfield , esta informação é transmitida apenas por meio de documentação. Além disso, o VTS pode realmente verificar se o valor dos sinalizadores é um OR bit a bit de Flag.

lidar com tipo primitivo

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

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

native_handle_t

O Android oferece suporte a native_handle_t , um conceito de identificador geral 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 arquivo que são passados ​​por valor. Um único descritor de arquivo pode ser armazenado em um identificador nativo sem ints e um único descritor de arquivo. A passagem 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 handle gera um ponteiro para um native_handle_t alocado separadamente.

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

Propriedade de identificador e descritor de arquivo

Quando você chama um método de interface HIDL que passa (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 chamador passando um objeto hidl_handle como um argumento retém a propriedade dos descritores de arquivo contidos no native_handle_t que ele envolve; o chamador deve fechar esses descritores de arquivo quando terminar com eles.
  • O processo que retorna um objeto hidl_handle (passando-o para uma função _cb ) mantém a propriedade dos descritores de arquivo contidos no native_handle_t pelo objeto; o processo deve fechar esses descritores de arquivo quando terminar com eles.
  • Um transporte que recebe um hidl_handle tem a propriedade dos descritores de arquivo dentro do native_handle_t pelo objeto; o receptor pode usar esses descritores de arquivo como estão durante o retorno de chamada da transação, mas deve clonar o identificador nativo para usar os descritores de arquivo além do retorno de chamada. O transporte close() os descritores de arquivo quando a transação for concluída.

HIDL não suporta handles em Java (já que Java não suporta handles).

Matrizes dimensionadas

Para matrizes dimensionadas em estruturas HIDL, seus elementos podem ser de qualquer tipo que uma estrutura possa conter:

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

Cordas

Strings aparecem de forma diferente em C++ e Java, mas o tipo de armazenamento de transporte subjacente é uma estrutura C++. Para obter detalhes, consulte Tipos de dados HIDL C++ ou Tipos de dados Java HIDL .

Nota: Passar uma string de ou para Java por meio de uma interface HIDL (incluindo Java para Java) causará conversões de conjunto de caracteres que podem não preservar exatamente a codificação original.

modelo de tipo vec<T>

O modelo vec<T> representa um buffer de tamanho variável contendo instâncias de T .

T pode ser um dos seguintes:

  • Tipos primitivos (por exemplo, uint32_t)
  • Cordas
  • Enumerações definidas pelo usuário
  • Estruturas definidas pelo usuário
  • Interfaces, ou a palavra-chave interface ( vec<IFoo> , vec<interface> é suportado apenas como um parâmetro de nível superior)
  • Alças
  • campo de bits<U>
  • vec<U>, onde U está nesta lista, exceto interface (por exemplo, vec<vec<IFoo>> não é suportado)
  • U[] (matriz de tamanho de U), onde U está nesta lista, exceto interface

Tipos definidos pelo usuário

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

Enum

HIDL não oferece suporte a enums anônimos. Caso contrário, as enumerações no HIDL são semelhantes ao C++ 11:

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

Um enum base é definido em termos de um dos tipos inteiros em HIDL. Se nenhum valor for especificado para o primeiro enumerador de uma enumeração com base em um tipo inteiro, o valor será padronizado para 0. Se nenhum valor for especificado para um enumerador posterior, o valor será padronizado para o valor anterior mais um. Por exemplo:

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

Um enum também pode herdar de um enum definido anteriormente. Se nenhum valor for especificado para o primeiro enumerador de uma enumeração filho (neste caso FullSpectrumColor ), o padrão será o valor do último enumerador da enumeração pai mais um. Por exemplo:

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

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

Os valores de enums 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 há necessidade de especificar o tipo se o valor for referenciado no mesmo tipo de enumeração ou tipos filho. 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 enums 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.

Estrutura

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

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

Da mesma forma, a string pode estar contida em uma struct (os buffers associados são separados). No C++ gerado, as instâncias do tipo de identificador HIDL são representadas por meio de um ponteiro para o identificador nativo real, pois as instâncias do tipo de dados subjacente são de comprimento variável.

União

HIDL não suporta sindicatos anônimos. Caso contrário, as uniões são semelhantes a C.

As uniões não podem conter tipos de correção (ponteiros, descritores de arquivo, objetos de fichário, etc.). Eles não precisam de campos especiais ou tipos associados e são simplesmente copiados via memcpy() ou equivalente. Uma união não pode conter diretamente (ou conter por meio de outras estruturas de dados) qualquer coisa que exija a configuração de deslocamentos do binder (ou seja, referências de interface de handle ou binder). Por exemplo:

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

As uniões também podem ser declaradas dentro de estruturas. Por 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
  }