Kiểu dữ liệu

Nội dung khai báo dữ liệu HIDL tạo ra cấu trúc dữ liệu bố cục chuẩn C++. Bạn có thể đặt các cấu trúc này ở bất kỳ vị trí nào cảm thấy tự nhiên (trên ngăn xếp, ở tệp hoặc phạm vi toàn cục hoặc trên vùng nhớ khối xếp) và có thể được kết hợp theo cùng một cách. Mã ứng dụng gọi mã proxy HIDL truyền vào các tham chiếu const và kiểu gốc, trong khi mã proxy và mã giả ẩn giấu thông tin chi tiết về quá trình chuyển đổi tuần tự.

Lưu ý: Mã do nhà phát triển viết không bao giờ bắt buộc phải chuyển đổi tuần tự hoặc huỷ chuyển đổi tuần tự cấu trúc dữ liệu một cách rõ ràng.

Bảng dưới đây liên kết các loại dữ liệu gốc HIDL với các loại dữ liệu C++:

Loại HIDL Loại C++ Tiêu đề/thư viện
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

Các phần dưới đây mô tả chi tiết hơn về các loại dữ liệu.

enum

Một enum trong HIDL sẽ trở thành một enum trong C++. Ví dụ:

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

… trở thành:

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

Kể từ Android 10, bạn có thể lặp lại một enum bằng cách sử dụng ::android::hardware::hidl_enum_range. Phạm vi này bao gồm mọi bộ đếm theo thứ tự xuất hiện trong mã nguồn HIDL, bắt đầu từ enum mẹ cho đến enum con cuối cùng. Ví dụ: mã này lặp lại WRITE, READ, NONECOMPARE theo thứ tự đó. Với SpecialMode ở trên:

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

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

hidl_enum_range cũng triển khai các trình duyệt ngược và có thể được sử dụng trong ngữ cảnh constexpr. Nếu một giá trị xuất hiện nhiều lần trong một enum, thì giá trị đó sẽ xuất hiện nhiều lần trong dải ô.

bitfield<T>

bitfield<T> (trong đó T là một enum do người dùng xác định) trở thành loại cơ bản của enum đó trong C++. Trong ví dụ trên, bitfield<Mode> trở thành uint8_t.

vec<T>

Mẫu lớp hidl_vec<T> là một phần của libhidlbase và có thể được dùng để truyền một vectơ của bất kỳ loại HIDL nào có kích thước tuỳ ý. Vùng chứa có kích thước cố định tương đương là hidl_array. Bạn cũng có thể khởi tạo hidl_vec<T> để trỏ đến vùng đệm dữ liệu bên ngoài thuộc loại T bằng cách sử dụng hàm hidl_vec::setToExternal().

Ngoài việc phát/chèn cấu trúc một cách thích hợp trong tiêu đề C++ đã tạo, việc sử dụng vec<T> sẽ tạo ra một số hàm thuận tiện để dịch đến/từ std::vector và con trỏ T trần. Nếu vec<T> được dùng làm tham số, thì hàm sử dụng tham số đó sẽ bị nạp chồng (2 nguyên mẫu được tạo) để chấp nhận và truyền cả cấu trúc HIDL và loại std::vector<T> cho tham số đó.

mảng

Mảng hằng số trong hidl được biểu thị bằng lớp hidl_array trong libhidlbase. hidl_array<T, S1, S2, …, SN> đại diện cho một mảng có kích thước cố định N chiều T[S1][S2]…[SN].

string

Bạn có thể sử dụng lớp hidl_string (một phần của libhidlbase) để truyền chuỗi qua các giao diện HIDL và được xác định trong /system/libhidl/base/include/hidl/HidlSupport.h. Vị trí lưu trữ đầu tiên trong lớp là con trỏ đến vùng đệm ký tự của lớp.

hidl_string biết cách chuyển đổi sang và từ std::string and char* (chuỗi kiểu C) bằng cách sử dụng operator=, truyền ngầm ẩn và hàm .c_str(). Cấu trúc chuỗi HIDL có các hàm khởi tạo sao chép và toán tử gán phù hợp để:

  • Tải chuỗi HIDL từ std::string hoặc chuỗi C.
  • Tạo một std::string mới từ một chuỗi HIDL.

Ngoài ra, các chuỗi HIDL có hàm khởi tạo chuyển đổi để các chuỗi C (char *) và chuỗi C++ (std::string) có thể được sử dụng trên các phương thức nhận chuỗi HIDL.

struct

struct trong HIDL chỉ có thể chứa các loại dữ liệu có kích thước cố định và không có hàm. Các định nghĩa cấu trúc HIDL liên kết trực tiếp với struct bố cục tiêu chuẩn trong C++, đảm bảo rằng struct có bố cục bộ nhớ nhất quán. Một cấu trúc có thể bao gồm các loại HIDL, bao gồm cả handle, stringvec<T>, trỏ đến các vùng đệm có độ dài biến riêng biệt.

tên người dùng

CẢNH BÁO: Địa chỉ thuộc bất kỳ loại nào (ngay cả địa chỉ thiết bị thực) không được nằm trong một tay cầm gốc. Việc truyền thông tin này giữa các quy trình là nguy hiểm và khiến các quy trình dễ bị tấn công. Mọi giá trị được truyền giữa các quy trình đều phải được xác thực trước khi được dùng để tra cứu bộ nhớ được phân bổ trong một quy trình. Nếu không, các tay điều khiển không hợp lệ có thể gây ra lỗi truy cập bộ nhớ hoặc hỏng bộ nhớ.

Loại handle được biểu thị bằng cấu trúc hidl_handle trong C++, đây là một trình bao bọc đơn giản xung quanh con trỏ đến đối tượng const native_handle_t (loại này đã có trong Android từ lâu).

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;

Theo mặc định, hidl_handle không sở hữu con trỏ native_handle_t mà nó bao bọc. Lớp này chỉ tồn tại để lưu trữ an toàn con trỏ đến native_handle_t để có thể sử dụng trong cả quy trình 32 và 64 bit.

Các trường hợp mà hidl_handle sở hữu các chỉ số mô tả tệp đi kèm bao gồm:

  • Sau lệnh gọi phương thức setTo(native_handle_t* handle, bool shouldOwn) với tham số shouldOwn được đặt thành true
  • Khi đối tượng hidl_handle được tạo bằng cách sao chép bản dựng từ một đối tượng hidl_handle khác
  • Khi đối tượng hidl_handle được sao chép-gán từ một đối tượng hidl_handle khác

hidl_handle cung cấp cả lượt chuyển đổi ngầm ẩn và tường minh đến/từ các đối tượng native_handle_t* . Mục đích chính của loại handle trong HIDL là truyền chỉ số mô tả tệp qua giao diện HIDL. Do đó, một chỉ số mô tả tệp được biểu thị bằng một native_handle_t không có int và một fd. Nếu ứng dụng khách và máy chủ hoạt động trong một quy trình khác nhau, thì quá trình triển khai RPC sẽ tự động xử lý chỉ số mô tả tệp để đảm bảo cả hai quy trình đều có thể hoạt động trên cùng một tệp.

Mặc dù chỉ số mô tả tệp mà một quy trình nhận được trong hidl_handle là hợp lệ trong quy trình đó, nhưng chỉ số này không tồn tại ngoài hàm nhận (được đóng khi hàm trả về). Một quy trình muốn giữ lại quyền truy cập liên tục vào chỉ số mô tả tệp phải dup() các chỉ số mô tả tệp được bao bọc hoặc sao chép toàn bộ đối tượng hidl_handle.

bộ nhớ

Loại memory HIDL liên kết với lớp hidl_memory trong libhidlbase, đại diện cho bộ nhớ dùng chung chưa được ánh xạ. Đây là đối tượng phải được truyền giữa các quy trình để chia sẻ bộ nhớ trong HIDL. Cách sử dụng bộ nhớ dùng chung:

  1. Lấy một thực thể của IAllocator (hiện chỉ có thực thể "ashmem") và sử dụng thực thể đó để phân bổ bộ nhớ dùng chung.
  2. IAllocator::allocate() trả về một đối tượng hidl_memory có thể được truyền qua HIDL RPC và được liên kết vào một quy trình bằng cách sử dụng hàm mapMemory của libhidlmemory.
  3. mapMemory trả về một tệp tham chiếu đến đối tượng sp<IMemory> có thể dùng để truy cập vào bộ nhớ. (IMemoryIAllocator được xác định trong android.hidl.memory@1.0.)

Bạn có thể sử dụng một thực thể của IAllocator để phân bổ bộ nhớ:

#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
  }));

Bạn phải thực hiện các thay đổi thực tế đối với bộ nhớ thông qua đối tượng IMemory, ở phía đã tạo mem hoặc ở phía nhận đối tượng đó qua 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();

giao diện

Bạn có thể truyền giao diện dưới dạng đối tượng. Bạn có thể sử dụng từ giao diện làm cú pháp đơn giản cho loại android.hidl.base@1.0::IBase; ngoài ra, giao diện hiện tại và mọi giao diện đã nhập đều được xác định là một loại.

Các biến chứa Giao diện phải là con trỏ mạnh: sp<IName>. Các hàm HIDL nhận tham số giao diện sẽ chuyển đổi con trỏ thô thành con trỏ mạnh, gây ra hành vi không trực quan (con trỏ có thể bị xoá một cách không mong muốn). Để tránh sự cố, hãy luôn lưu trữ giao diện HIDL dưới dạng sp<>.