Tipos de dados

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

Observação: em nenhum momento o código escrito pelo desenvolvedor é necessário para serializar ou desserializar explicitamente as estruturas de dados.

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

Tipo 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 com mais detalhes.

enumerar

Uma enumeração em HIDL torna-se uma enumeração em C++. Por exemplo:

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

… torna-se:

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 enum pode ser iterado usando ::android::hardware::hidl_enum_range . Esse intervalo inclui cada enumerador na ordem em que aparecem no código-fonte HIDL, começando da enumeração pai até o último filho. Por exemplo, esse código itera sobre WRITE , READ , NONE e COMPARE nessa ordem. Dado 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, o valor aparecerá no intervalo várias vezes.

campo de bits<T>

bitfield<T> (onde T é uma enumeração definida pelo usuário) torna-se o tipo subjacente dessa enumeração em C++. No exemplo acima, bitfield<Mode> se torna uint8_t .

vec<T>

O modelo de classe hidl_vec<T> faz parte da libhidlbase e pode ser usado para passar um vetor de qualquer tipo 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 hidl_vec::setToExternal() .

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

variedade

Arrays constantes em hidl são representados 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] .

corda

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

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

  • Carregue a string HIDL de um std::string ou uma string C.
  • Crie um novo std::string de uma string HIDL.

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

estrutura

Uma struct em HIDL pode conter apenas tipos de dados de tamanho fixo e nenhuma função. As definições de struct HIDL são mapeadas diretamente para structs de layout padrão em C++, garantindo que struct 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.

lidar com

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.

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 (isso está 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 se apropria 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 possa ser usado em processos de 32 e 64 bits.

Cenários em que o hidl_handle possui seus descritores de arquivo incluídos incluem:

  • Após uma chamada ao 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 de/para objetos native_handle_t* . O principal uso para o tipo de handle em HIDL é passar descritores de arquivo por interfaces HIDL. Um único descritor de arquivo é, portanto, representado por um native_handle_t sem int s e um único fd . Se o cliente e o servidor estiverem em um processo diferente, a implementação de 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 persistirá além da função receptora (será fechado quando a função retornar). Um processo que deseja manter o acesso persistente ao descritor de arquivo deve dup() os descritores de arquivo incluídos ou copiar todo o objeto hidl_handle .

memória

O tipo de memory HIDL é mapeado para a classe hidl_memory em libhidlbase , que representa a memória compartilhada não mapeada. Este é o objeto que deve ser passado entre os processos para compartilhar memória em HIDL. Para usar a memória compartilhada:

  1. Obtenha uma instância de IAllocator (atualmente apenas a instância "ashmem" está disponível) e use-a para alocar memória compartilhada.
  2. IAllocator::allocate() retorna um objeto hidl_memory que pode ser passado através de HIDL RPC e ser mapeado em um processo usando a função libhidlmemory da mapMemory .
  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 alterações reais na memória devem ser feitas por meio de um objeto IMemory , seja no lado que criou o mem ou no lado que o recebe por HIDL RPC.

// 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

Interfaces podem ser passadas 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 quaisquer interfaces importadas serão definidas como um tipo.

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