Tipos de dados

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

As semelhanças com C++ incluem:

  • structs usam sintaxe C++; unions suportam a sintaxe C++ por padrão. Ambos devem ser nomeados; estruturas e sindicatos anônimos não são suportados.
  • Typedefs são permitidos em HIDL (assim 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 interface 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 Standard-Layout (um subconjunto do requisito de tipos de dados simples) tem um layout de memória consistente no código C++ gerado, aplicado com atributos de alinhamento explícitos em membros struct e union .

Os tipos HIDL primitivos, 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 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; arrays mapeados para arrays Java; sindicatos não são atualmente suportados em Java. As strings são armazenadas internamente como UTF8. Como Java suporta apenas cadeias de caracteres UTF16, os valores de cadeia de caracteres enviados de ou para uma implementação Java são traduzidos e podem não ser idênticos na nova tradução, pois os conjuntos de caracteres nem sempre são mapeados suavemente.

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 back-end do 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 VTS Compiler (VTSC).

As anotações usam 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.

Declarações futuras

Em HIDL, as estruturas não podem ser declaradas posteriormente, 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 dos HALs existentes (pré-Android 8.x) tem uso limitado de declarações futuras, que podem ser removidas reorganizando as declarações da 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 rastrear valores de ponteiro que podem ocorrer várias vezes em uma estrutura de dados autorreferencial. 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 serão feitas e entregues.

Declarações aninhadas

HIDL suporta declarações aninhadas em quantos níveis desejar (com uma exceção mencionada 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 em um nível de profundidade (sem vec<vec<IFoo>> ).

Sintaxe de ponteiro bruto

A linguagem HIDL não usa * e não suporta a flexibilidade total dos 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.

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

Por exemplo, IServiceManager possui o seguinte método:

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

O método promete procurar alguma interface por nome. Também é idêntico substituir interface por 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, estruturas, matrizes ou uniões aninhadas.

MQDescriptorSync e MQDescriptorUnsync

Os tipos MQDescriptorSync e MQDescriptorUnsync transmitem descritores 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. É compatível apenas com C++. Um valor desse tipo pode ser usado no receptor 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 poderão sofrer problemas fatais de compatibilidade.

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 é 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 lida com 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 ao leitor, que agora sabe que setFlags assume 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 através 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 consultar a memória alocada em um processo. Caso contrário, identificadores incorretos poderão 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 pedaços de dados, ou dados que precisam ser compartilhados entre processos (como um limite de sincronização), são tratados passando 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 um descritor de arquivo. O driver do fichário duplica o descritor de arquivo em outro processo.

identificador_nativo_t

O Android oferece suporte native_handle_t , um conceito geral de identificador 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 inteiros e descritores de arquivo que são transmitidos por valor. Um único descritor de arquivo pode ser armazenado em um identificador nativo sem ints e com um único descritor de arquivo. Passar 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 possui tamanho variável, ele não pode ser incluído diretamente em uma estrutura. Um campo handle gera um ponteiro para um native_handle_t alocado separadamente.

Nas versões anteriores do Android, os identificadores nativos eram criados usando as mesmas funções presentes em libcutils . No Android 8.0 e versões posteriores, 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 desserializa essas funções automaticamente, sem envolvimento do código escrito pelo usuário.

Propriedade do identificador e do 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 que passa um objeto hidl_handle como argumento retém a propriedade dos descritores de arquivo contidos no native_handle_t que ele agrupa; 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 ) retém a propriedade dos descritores de arquivo contidos no native_handle_t encapsulados pelo objeto; o processo deve fechar esses descritores de arquivo quando terminar com eles.
  • Um transporte que recebe um hidl_handle possui propriedade dos descritores de arquivo dentro do native_handle_t encapsulados 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 oferece suporte a identificadores em Java (já que Java não oferece suporte a identificadores).

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

As strings aparecem de maneira 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 HIDL Java .

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
  • Enums definidos pelo usuário
  • Estruturas definidas pelo usuário
  • Interfaces ou a palavra-chave interface ( vec<IFoo> , vec<interface> é suportada apenas como 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 enumerações anônimas. Caso contrário, enums em HIDL são semelhantes a C++ 11:

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

Uma enumeração base é definida em termos de um dos tipos inteiros em 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. 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 previamente definido. 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 enum funciona de forma inversa em relação à maioria dos outros tipos de herança. Um valor de enum filho não pode ser usado como um valor de enum pai. Isso ocorre porque um enum filho inclui mais valores que o pai. No entanto, um valor de enum pai pode ser usado com segurança como um valor de enum filho porque os valores de enum filho são, por definição, um superconjunto de valores de enum pai. Tenha isso em mente ao projetar interfaces, pois isso significa que os tipos que se referem a enums pais não podem se referir a enums filhos 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 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, as enumerações têm um atributo len que pode ser usado em expressões constantes. MyEnum::len é o número total de entradas nessa enumeração. Isto é 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 inteiramente em uma estrutura. Isso inclui a matriz de comprimento indefinido que às vezes é usada como o último campo de uma estrutura em C/C++ (às vezes vista com um tamanho de [0] ). HIDL vec<T> representa matrizes de tamanho dinâmico com os dados armazenados em um buffer separado; tais instâncias são representadas com uma instância de vec<T> na struct .

Da mesma forma, string pode estar contida em uma struct (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 têm comprimento variável.

União

HIDL não oferece suporte a sindicatos anônimos. Caso contrário, os sindicatos são semelhantes a C.

As uniões não podem conter tipos de correção (ponteiros, descritores de arquivos, objetos fichários, 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 através de outras estruturas de dados) nada que exija a definição de deslocamentos de fichário (ou seja, referências de identificador ou de interface de fichário). 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
  }