Tipos de dados

As declarações de dados HIDL geram estruturas de dados de layout padrão em C++. Essas estruturas podem ser colocadas em qualquer lugar que pareça natural (na pilha, no arquivo ou no escopo global ou na pilha) e podem ser compostas da mesma maneira. O código do cliente chama o código de proxy HIDL transmitindo referências const e tipos primitivos, enquanto o código de stub e proxy oculta os detalhes da serialização.

Observação:em nenhum momento é necessário que o código escrito pelo desenvolvedor seja serializado ou desserializado explicitamente.

A tabela abaixo mapeia primitivas HIDL para tipos de dados C++:

Tipo de HIDL Tipo C++ Cabeçalho/biblioteca
enum enum class
uint8_t..uint64_t uint8_t..uint64_t <stdint.h>
int8_t..int64_t int8_t..int64_t <stdint.h>
float float
double double
vec<T> hidl_vec<T> libhidlbase
T[S1][S2]...[SN] T[S1][S2]...[SN]
string hidl_string libhidlbase
handle hidl_handle libhidlbase
safe_union (custom) struct
struct struct
union union
fmq_sync MQDescriptorSync libhidlbase
fmq_unsync MQDescriptorUnsync libhidlbase

As seções abaixo descrevem os tipos de dados em mais detalhes.

enum

Um tipo enumerado em HIDL se torna um tipo enumerado em C++. Por exemplo:

enum Mode : uint8_t { WRITE = 1 << 0, READ = 1 << 1 };
enum SpecialMode : Mode { NONE = 0, COMPARE = 1 << 2 };

… se torna:

enum class Mode : uint8_t { WRITE = 1, READ = 2 };
enum class SpecialMode : uint8_t { WRITE = 1, READ = 2, NONE = 0, COMPARE = 4 };

A partir do Android 10, um tipo enumerado pode ser iterado usando ::android::hardware::hidl_enum_range. Esse intervalo inclui todos os enumeradores na ordem em que aparecem no código-fonte HIDL, começando pelo tipo enumerado pai até o último filho. Por exemplo, este código itera sobre WRITE, READ, NONE e COMPARE nessa ordem. Considerando SpecialMode acima:

template <typename T>
using hidl_enum_range = ::android::hardware::hidl_enum_range<T>

for (SpecialMode mode : hidl_enum_range<SpecialMode>) {...}

hidl_enum_range também implementa iteradores reversos e pode ser usado em contextos constexpr. Se um valor aparecer em uma enumeração várias vezes, ele vai aparecer no intervalo várias vezes.

bitfield<T>

bitfield<T> (em que T é um tipo enumerado definido pelo usuário) se torna o tipo subjacente desse tipo enumerado em C++. No exemplo acima, bitfield<Mode> se torna uint8_t.

vec<T>

O modelo de classe hidl_vec<T> faz parte de libhidlbase e pode ser usado para transmitir um vetor de qualquer tipo de HIDL com um tamanho arbitrário. O contêiner de tamanho fixo comparável é hidl_array. Um hidl_vec<T> também pode ser inicializado para apontar para um buffer de dados externo do tipo T, usando a função hidl_vec::setToExternal().

Além de emitir/inserir o struct corretamente no cabeçalho C++ gerado, o uso de vec<T> gera algumas funções de conveniência para traduzir para/de std::vector e ponteiros T simples. Se o vec<T> for usado como um parâmetro, a função que o usa será sobrecarregada (dois protótipos são gerados) para aceitar e transmitir o struct HIDL e um tipo std::vector<T> para esse parâmetro.

matriz

Matrizes constantes no hidl são representadas pela classe hidl_array em libhidlbase. Um hidl_array<T, S1, S2, …, SN> representa uma matriz de tamanho fixo N-dimensional T[S1][S2]…[SN].

string

A classe hidl_string (parte de libhidlbase) pode ser usada para transmitir strings por interfaces HIDL e é definida em /system/libhidl/base/include/hidl/HidlSupport.h. O primeiro local de armazenamento na classe é um ponteiro para o buffer de caracteres.

hidl_string sabe como converter de e para std::string and char* (string no estilo C) usando operator=, casts implícitos e a função .c_str(). As estruturas de string de HIDL têm os construtores de cópia e operadores de atribuição apropriados para:

  • Carregue a string HIDL de uma std::string ou uma string C.
  • Crie uma nova std::string a partir de uma string HIDL.

Além disso, as strings HIDL têm construtores de conversão para que as strings C (char *) e C++ (std::string) possam ser usadas em métodos que recebem uma string HIDL.

struct

Uma struct no HIDL pode conter apenas tipos de dados de tamanho fixo e nenhuma função. As definições de estrutura HIDL são mapeadas diretamente para structs de layout padrão em C++, garantindo que as structs tenham um layout de memória consistente. Um struct pode incluir tipos HIDL, incluindo handle, string e vec<T>, que apontam para buffers de comprimento variável separados.

identificador

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 inválidos podem causar acesso incorreto à memória ou corrupção de memória.

O tipo handle é representado pela estrutura hidl_handle em C++, que é um wrapper simples em torno de um ponteiro para um objeto const native_handle_t (presente no Android há muito tempo).

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;

Por padrão, hidl_handle não assume a propriedade do ponteiro native_handle_t que ele envolve. Ele existe apenas para armazenar com segurança um ponteiro para um native_handle_t, de modo que ele possa ser usado em processos de 32 e 64 bits.

Os cenários em que o hidl_handle é proprietário dos descritores de arquivo anexados incluem:

  • Após uma chamada para o método setTo(native_handle_t* handle, bool shouldOwn) com o parâmetro shouldOwn definido como true
  • Quando o objeto hidl_handle é criado pela construção de cópia de outro objeto hidl_handle
  • Quando o objeto hidl_handle é atribuído por cópia de outro objeto hidl_handle

hidl_handle fornece conversões implícitas e explícitas para/de objetos native_handle_t* . O uso principal do tipo handle no HIDL é transmitir descritores de arquivo por interfaces HIDL. Portanto, um único descritor de arquivo é representado por um native_handle_t sem ints e um único fd. Se o cliente e o servidor estiverem em um processo diferente, a implementação do RPC cuidará automaticamente do descritor de arquivo para garantir que ambos os processos possam operar no mesmo arquivo.

Embora um descritor de arquivo recebido em um hidl_handle por um processo seja válido nesse processo, ele não persiste além da função recebedora (ele é fechado quando a função retorna). Um processo que quer manter o acesso persistente ao descritor de arquivo precisa dup() os descritores de arquivo incluídos ou copiar todo o objeto hidl_handle.

memória

O tipo memory do HIDL é mapeado para a classe hidl_memory em libhidlbase, que representa a memória compartilhada não mapeada. Esse é o objeto que precisa ser transmitido entre os processos para compartilhar memória no HIDL. Para usar a memória compartilhada:

  1. Receba uma instância de IAllocator (atualmente, apenas a instância "ashmem" está disponível) e use-a para alocar a memória compartilhada.
  2. IAllocator::allocate() retorna um objeto hidl_memory que pode ser transmitido pelo RPC HIDL e mapeado em um processo usando a função mapMemory do libhidlmemory.
  3. mapMemory retorna uma referência a um objeto sp<IMemory> que pode ser usado para acessar a memória. (IMemory e IAllocator são definidos em android.hidl.memory@1.0.)

Uma instância de IAllocator pode ser usada para alocar memória:

#include <android/hidl/allocator/1.0/IAllocator.h>
#include <android/hidl/memory/1.0/IMemory.h>
#include <hidlmemory/mapping.h>
using ::android::hidl::allocator::V1_0::IAllocator;
using ::android::hidl::memory::V1_0::IMemory;
using ::android::hardware::hidl_memory;
....
  sp<IAllocator> ashmemAllocator = IAllocator::getService("ashmem");
  ashmemAllocator->allocate(2048, [&](bool success, const hidl_memory& mem) {
        if (!success) { /* error */ }
        // now you can use the hidl_memory object 'mem' or pass it around
  }));

As mudanças reais na memória precisam ser feitas por um objeto IMemory, seja no lado que criou mem ou no lado que o recebe por RPC HIDL.

// Same includes as above

sp<IMemory> memory = mapMemory(mem);
void* data = memory->getPointer();
memory->update();
// update memory however you wish after calling update and before calling commit
data[0] = 42;
memory->commit();
// …
memory->update(); // the same memory can be updated multiple times
// …
memory->commit();

interface

As interfaces podem ser transmitidas como objetos. A palavra interface pode ser usada como açúcar sintático para o tipo android.hidl.base@1.0::IBase. Além disso, a interface atual e todas as interfaces importadas são definidas como um tipo.

As variáveis que contêm interfaces precisam ser ponteiros fortes: sp<IName>. As funções HIDL que usam parâmetros de interface convertem ponteiros brutos em ponteiros fortes, causando um comportamento não intuitivo (o ponteiro pode ser limpo inesperadamente). Para evitar problemas, sempre armazene interfaces HIDL como um sp<>.