新增 C++ 繫結

新的 C++ 繫結有「自然」和「線路」兩種變體。自然綁帶注重安全和人體工學,而線材綁帶則注重效能。大部分程式碼都應使用自然繫結。 只有在效能要求需要或需要精確控制記憶體配置時,才使用線路繫結。

程式庫

假設程式庫宣告如下:

library fuchsia.examples;

通訊協定型別會在 fuchsia_examples 命名空間中產生。 這個程式庫的網域物件會在 fuchsia_examples::wire 命名空間中產生,測試支架則會在 fidl::testing 命名空間中產生。

產生的型別名稱會經過轉換,以符合 Google C++ 樣式指南

常數

常數會產生為 constexpr。舉例來說,下列常數:

const BOARD_SIZE uint8 = 9;
const NAME string = "Tic-Tac-Toe";

在標頭檔案中產生,如下所示:

constexpr uint8_t kBoardSize = 9u;
extern const char[] kName;

FIDL 基本型別與 C++ 型別的對應關係,請參閱內建型別。字串會宣告為標頭檔案中的 extern const char[],並定義在 .cc 檔案中,而不是 constexpr

欄位

本節說明 FIDL 工具鍊如何將 FIDL 型別轉換為 C++ 傳輸型別。這些型別可以顯示為匯總型別中的成員,或是通訊協定方法的參數。

內建型別

系統會根據下表將 FIDL 型別轉換為 C++ 型別:

FIDL 類型 C++ 電線類型
bool bool(需要 sizeof(bool) == 1)
int8 int8_t
int16 int16_t
int32 int32_t
int64 int64_t
uint8 uint8_t
uint16 uint16_t
uint32 uint32_t
uint64 uint64_t
float32 float
float64 double
array<T, N> fidl::Array<T, N>
vector<T>:N fidl::VectorView<T>
string fidl::StringView
client_end:P fidl::ClientEnd<P>
server_end:P fidl::ServerEnd<P>
zx.Handle zx::handle
zx.Handle:S 盡可能使用對應的 zx 類型。例如 zx::vmozx::channel

選用向量、字串、用戶端/伺服器結尾和控制代碼的 C++ 傳輸線型別,與非選用對應項目相同。

使用者定義型別

C++ 連結線繫結會為每個使用者定義的位元、列舉、結構體、表格和聯集定義類別。如果是嚴格列舉,系統會定義 enum class,而不是一般類別。對於聯集,它們會使用相同的類別來表示選用和非選用值。如果是裝箱結構體,則使用 fidl::ObjectView。如要進一步瞭解 fidl::ObjectView,請參閱「記憶體擁有權的線路網域物件」。

類型定義

點數

假設 bits 定義如下:

type FileMode = strict bits : uint16 {
    READ = 0b001;
    WRITE = 0b010;
    EXECUTE = 0b100;
};

FIDL 工具鍊會為每個標記產生具有靜態成員的 FileMode 類別,以及包含所有位元成員遮罩的 kMask 成員 (在本例中為 0b111):

  • const static FileMode kRead
  • const static FileMode kWrite
  • const static FileMode kExecute
  • const static FileMode kMask

FileMode 提供下列方法:

  • explicit constexpr FileMode(uint16_t):從基礎原始值建構值,並保留任何不明位元成員。
  • constexpr static std::optional<FileMode> TryFrom(uint16_t value):如果值不含任何不明成員,則會從基礎原始值建構位元例項,否則會傳回 std::nullopt
  • constexpr static FileMode TruncatingUnknown(uint16_t value):從基礎原始值建構位元例項,並清除任何不明成員。
  • 位元運算子:提供 ||=&&=^^=~ 運算子的實作項目,可對位元執行位元運算,例如 mode |= FileMode::kExecute
  • 比較運算子 ==!=
  • uint16_tbool 的明確轉換函式。

如果 FileMode 是「彈性」,則會包含下列額外方法:

  • constexpr FileMode unknown_bits() const:傳回的位元值只包含這個位元值中的不明成員。
  • constexpr bool has_unknown_bits() const:傳回這個值是否包含任何不明位元。

使用範例:

static_assert(std::is_same<fuchsia_examples::FileMode, fuchsia_examples::wire::FileMode>::value,
              "natural bits should be equivalent to wire bits");
static_assert(fuchsia_examples::FileMode::kMask == fuchsia_examples::wire::FileMode::kMask,
              "natural bits should be equivalent to wire bits");

using fuchsia_examples::wire::FileMode;
auto flags = FileMode::kRead | FileMode::kWrite | FileMode::kExecute;
ASSERT_EQ(flags, FileMode::kMask);

列舉

假設有以下 enum 定義:

type LocationType = strict enum {
    MUSEUM = 1;
    AIRPORT = 2;
    RESTAURANT = 3;
};

FIDL 工具鍊會使用指定的基礎型別產生 C++ enum class,如果未指定基礎型別,則會產生 uint32_t

enum class LocationType : uint32_t {
    kMuseum = 1u;
    kAirport = 2u;
    kRestaurant = 3u;
};

使用範例:

static_assert(
    std::is_same<fuchsia_examples::LocationType, fuchsia_examples::wire::LocationType>::value,
    "natural enums should be equivalent to wire enums");

ASSERT_EQ(static_cast<uint32_t>(fuchsia_examples::wire::LocationType::kMuseum), 1u);

彈性列舉

彈性列舉會實作為 class,而非 enum class,並採用下列方法:

  • constexpr LocationType():預設建構函式,可將列舉初始化為未指定的未知值。
  • constexpr LocationType(uint32_t value):明確的建構函式,會採用列舉基礎型別的值。
  • constexpr bool IsUnknown():傳回列舉值是否不明。
  • constexpr static LocationType Unknown():傳回保證會視為不明的列舉值。如果列舉的成員已使用 [Unknown] 註解,則會傳回該成員的值。如果沒有這類成員,則傳回的列舉成員基礎值未指定。
  • explicit constexpr operator int32_t() const:將列舉轉換回基礎值。

產生的類別會為每個列舉成員提供靜態成員,保證與對等嚴格列舉中的 enum class 成員相符:

  • const static LocationType kMuseum
  • const static LocationType kAirport
  • const static LocationType kRestaurant

Structs

假設有下列 struct 宣告:

type Color = struct {
    id uint32;
    @allow_deprecated_struct_defaults
    name string:MAX_STRING_LENGTH = "red";
};

FIDL 工具鍊會產生等效的 struct

struct Color {
    uint32_t id = {};
    fidl::StringView name = {};
}

C++ 繫結不支援預設值,而是將結構體的全部欄位初始化為零。

使用範例:

// Wire structs are simple C++ structs with all their member fields declared
// public. One may invoke aggregate initialization:
fuchsia_examples::wire::Color blue = {1, "blue"};
ASSERT_EQ(blue.id, 1u);
ASSERT_EQ(blue.name.get(), "blue");

// ..or designated initialization.
fuchsia_examples::wire::Color blue_designated = {.id = 1, .name = "blue"};
ASSERT_EQ(blue_designated.id, 1u);
ASSERT_EQ(blue_designated.name.get(), "blue");

// A wire struct may be default constructed, but user-defined default values
// are not supported.
// Default-initializing a struct means all fields are zero-initialized.
fuchsia_examples::wire::Color default_color;
ASSERT_EQ(default_color.id, 0u);
ASSERT_TRUE(default_color.name.is_null());
ASSERT_TRUE(default_color.name.empty());

// There are no getters/setters. One simply reads or mutates the member field.
blue.id = 2;
ASSERT_EQ(blue.id, 2u);

// Here we demonstrate that wire structs do not own their out-of-line children.
// Copying a struct will not copy their out-of-line children. Pointers are
// simply aliased.
{
  fuchsia_examples::wire::Color blue2 = blue;
  ASSERT_EQ(blue2.name.data(), blue.name.data());
}
// Similarly, destroying a wire struct object does not destroy out-of-line
// children. Destroying |blue2| does not invalidate the string contents in |name|.
ASSERT_EQ(blue.name.get(), "blue");

聯集

假設有以下聯集定義:

type JsonValue = strict union {
    1: int_value int32;
    2: string_value string:MAX_STRING_LENGTH;
};

FIDL 會產生 JsonValue 類別。JsonValue 包含代表可能變體的公開標記列舉類別:

enum class Tag : fidl_xunion_tag_t {
  kIntValue = 2,
  kStringValue = 3,
};

Tag 的每個成員都有一個值,與 union 定義中指定的序數相符。

JsonValue 提供下列方法:

  • JsonValue():預設建構函式。建構的聯集一開始處於「缺席」狀態,直到設定變體為止。建議盡可能使用 WithFoo 建構函式。
  • ~JsonValue():清除基礎聯集資料的解構函式。
  • JsonValue(JsonValue&&):預設移動建構函式。
  • JsonValue& operator=(JsonValue&&):預設移動指派
  • static JsonValue WithIntValue(fidl::ObjectView<int32>)static JsonValue WithStringValue(fidl::ObjectView<fidl::StringView>):靜態建構函式,可直接建構聯集特定變體。
  • bool has_invalid_tag():如果 JsonValue 執行個體尚未設定變體,則會傳回 true。如果未先設定變數就呼叫這個方法,就會導致斷言錯誤。
  • bool is_int_value() constbool is_string_value() const:每個變體都有相關聯的方法,可檢查 JsonValue 的執行個體是否屬於該變體
  • const int32_t& int_value() constconst fidl::StringView& string_value() const:每個變體的唯讀存取子方法。如果未先設定變體就呼叫這些方法,會導致斷言錯誤。
  • int32_t& int_value()fidl::StringView& string_value():各變體的變動存取子方法。如果 JsonValue 沒有指定的變數集,這些方法就會失敗
  • Tag Which() const:傳回 JsonValue 的目前標記。如果未先設定變體就呼叫這個方法,會導致斷言錯誤。

使用範例:

// When the active member is larger than 4 bytes, it is stored out-of-line,
// and the union will borrow the out-of-line content. The lifetimes can be
// tricky to reason about, hence the FIDL runtime provides a |fidl::AnyArena|
// interface for arena-based allocation of members. The built-in
// implementation is |fidl::Arena|.
//
// Pass the arena as the first argument to |With...| factory functions, to
// construct the member content on the arena, and have the union reference it.
fidl::Arena arena;
fuchsia_examples::wire::JsonValue str_union =
    fuchsia_examples::wire::JsonValue::WithStringValue(arena, "1");

// |Which| obtains an enum corresponding to the active member, which may be
// used in switch cases.
ASSERT_EQ(str_union.Which(), fuchsia_examples::wire::JsonValue::Tag::kStringValue);

// Before accessing the |string_value| member, one should check if the union
// indeed currently holds this member, by querying |is_string_value|.
// Accessing the wrong member will cause a panic.
ASSERT_TRUE(str_union.is_string_value());
ASSERT_EQ("1", str_union.string_value().get());

// When the active member is smaller or equal to 4 bytes, such as an
// |int32_t| here, the entire member is inlined into the union object.
// In these cases, arena allocation is not necessary, and the union
// object wholly owns the member.
fuchsia_examples::wire::JsonValue int_union = fuchsia_examples::wire::JsonValue::WithIntValue(1);
ASSERT_TRUE(int_union.is_int_value());
ASSERT_EQ(1, int_union.int_value());

// A default constructed wire union is invalid.
// It must be initialized with a valid member before use.
// One is not allowed to send invalid unions through FIDL client/server APIs.
fuchsia_examples::wire::JsonValue default_union;
ASSERT_TRUE(default_union.has_invalid_tag());
default_union = fuchsia_examples::wire::JsonValue::WithStringValue(arena, "hello");
ASSERT_FALSE(default_union.has_invalid_tag());
ASSERT_TRUE(default_union.is_string_value());
ASSERT_EQ(default_union.string_value().get(), "hello");

// Optional unions are represented with |fidl::WireOptional|.
fidl::WireOptional<fuchsia_examples::wire::JsonValue> optional_json;
ASSERT_FALSE(optional_json.has_value());
optional_json = fuchsia_examples::wire::JsonValue::WithIntValue(42);
ASSERT_TRUE(optional_json.has_value());
// |fidl::WireOptional| has a |std::optional|-like API.
fuchsia_examples::wire::JsonValue& value = optional_json.value();
ASSERT_TRUE(value.is_int_value());

// When switching over the tag from a flexible union, one must add a `default:`
// case, to handle members not understood by the FIDL schema or to handle
// newly added members in a source compatible way.
fuchsia_examples::wire::FlexibleJsonValue flexible_value =
    fuchsia_examples::wire::FlexibleJsonValue::WithIntValue(1);
switch (flexible_value.Which()) {
  case fuchsia_examples::wire::FlexibleJsonValue::Tag::kIntValue:
    ASSERT_EQ(flexible_value.int_value(), 1);
    break;
  case fuchsia_examples::wire::FlexibleJsonValue::Tag::kStringValue:
    FAIL() << "Unexpected tag. |flexible_value| was set to int";
    break;
  default:  // Removing this branch will fail to compile.
    break;
}

彈性聯集和不明變體

彈性聯集在產生的 Tag 類別中,會多出一個變體:

  enum class Tag : fidl_xunion_tag_t {
    ... // other fields omitted
    kUnknown = ::std::numeric_limits<::fidl_union_tag_t>::max(),
  };

當含有未知變體的聯集 FIDL 訊息解碼為 JsonValue 時,JsonValue::Which() 會傳回 JsonValue::Tag::kUnknown

C++ 繫結不會儲存原始位元組,也不會處理不明變數的控制碼。

系統不支援使用不明變體編碼聯集,否則會導致編碼失敗。

表格

假設有以下資料表定義:

type User = table {
    1: age uint8;
    2: name string:MAX_STRING_LENGTH;
};

FIDL 工具鍊會產生 User 類別,其中包含下列方法:

  • User():預設建構函式,會初始化空白資料表,且未設定任何欄位。
  • User::Builder(fidl::AnyArena& arena):建構工具工廠。 傳回 fidl::WireTableBuilder<User>,從提供的競技場分配影格和成員。
  • User::ExternalBuilder(fidl::ObjectView<fidl::WireTableFrame<User>> frame): 外部建構工具工廠。傳回隨附影格的 fidl::WireTableExternalBuilder<User>。這個建構工具需要謹慎管理記憶體,但偶爾可能會有用。買者自負風險
  • User(User&&):預設移動建構函式。
  • ~User():預設解構函式。
  • User& operator=(User&&):預設移動指派。
  • bool IsEmpty() const:如果未設定任何欄位,則傳回 true。
  • bool has_age() constbool has_name() const:傳回欄位是否已設定。
  • const uint8_t& age() constconst fidl::StringView& name() const:唯讀欄位存取子方法。如果未先設定欄位就呼叫這些方法,會導致斷言錯誤。

如要建構資料表,系統會產生三個額外類別:fidl::WireTableBuilder<User>fidl::WireTableExternalBuilder<User>fidl::WireTableFrame<User>

fidl::WireTableFrame<User> 是資料表內部儲存空間的容器,與建構工具分開配置,可維護基礎線路格式的物件版面配置。僅供建構人員內部使用。

fidl::WireTableFrame<User> 具有下列方法:

  • WireTableFrame():預設建構函式。

fidl::WireTableExternalBuilder<User> 具有下列方法:

  • fidl::WireTableExternalBuilder<User> age(uint8_t): 將年齡內嵌至表格影格中,即可設定年齡。
  • fidl::WireTableExternalBuilder<User> name(fidl::ObjectView<fidl::StringView>):設定名稱,並使用已分配的值。
  • User Build():建構並傳回資料表物件。呼叫 Build() 後,必須捨棄建構工具。

fidl::WireTableBuilder<User> 具有 fidl::WireTableExternalBuilder<User> 的所有方法 (但具有來自設定器的正確傳回型別),並新增:

  • fidl::WireTableBuilder<User> name(std::string_view):從建構工具的競技場分配新的 fidl::StringView,然後將提供的字串複製到其中,即可設定名稱。

使用範例:

fidl::Arena arena;
// To construct a wire table, you need to first create a corresponding
// |Builder| object, which borrows an arena. The |arena| will be used to
// allocate the table frame, a bookkeeping structure for field presence.
auto builder = fuchsia_examples::wire::User::Builder(arena);

// To set a table field, call the member function with the same name on the
// builder. The arguments will be forwarded to the field constructor, and the
// field is allocated on the initial |arena|.
builder.age(10);

// Note that only the inline portion of the field is automatically placed in
// the arena. The field itself may reference its own out-of-line content,
// such as in the case of |name| whose type is |fidl::StringView|. |name|
// will reference the "jdoe" literal, which lives in static program storage.
builder.name("jdoe");

// Call |Build| to finalize the table builder into a |User| table.
// The builder is no longer needed after this point. |user| will continue to
// reference objects allocated in the |arena|.
fuchsia_examples::wire::User user = builder.Build();
ASSERT_FALSE(user.IsEmpty());

// Before accessing a field, one should check if it is present, by querying
// |has_...|. Accessing an absent field will panic.
ASSERT_TRUE(user.has_name());
ASSERT_EQ(user.name().get(), "jdoe");

// Setters may be chained, leading to a fluent syntax.
user = fuchsia_examples::wire::User::Builder(arena).age(30).name("bob").Build();
ASSERT_FALSE(user.IsEmpty());
ASSERT_TRUE(user.has_age());
ASSERT_EQ(user.age(), 30);
ASSERT_TRUE(user.has_name());
ASSERT_EQ(user.name().get(), "bob");

// A default constructed wire table is empty.
// This is mostly useful to make requests or replies with empty tables.
fuchsia_examples::wire::User defaulted_user;
ASSERT_TRUE(defaulted_user.IsEmpty());

// In some situations it could be difficult to provide an arena when
// constructing tables. For example, here it is hard to provide constructor
// arguments to 10 tables at once. Because a default constructed wire table is
// empty, a new table instance should be built and assigned in its place.
fidl::Array<fuchsia_examples::wire::User, 10> users;
for (auto& user : users) {
  ASSERT_TRUE(user.IsEmpty());
  user = fuchsia_examples::wire::User::Builder(arena).age(30).Build();
  ASSERT_FALSE(user.IsEmpty());
  ASSERT_EQ(user.age(), 30);
}
ASSERT_EQ(users[0].age(), 30);

// Finally, tables support checking if it was received with unknown fields.
// A table created by ourselves will never have unknown fields.
ASSERT_FALSE(user.HasUnknownData());

除了使用 fidl::ObjectView 指派欄位,您也可以使用「線路網域物件的記憶體擁有權」一文所述的任何分配策略。

內嵌版面配置

產生的 C++ 程式碼會使用fidlc為內嵌版面配置選擇的名稱。

C++ 繫結也會產生範圍名稱,用來參照內嵌版面配置。舉例來說,針對 FIDL:

type Outer = struct {
  inner struct {};
};

您可以使用全域不重複名稱 Inner 和範圍名稱 Outer::Inner,參照內部結構體。如果使用 @generated_name 屬性覆寫頂層名稱,這項功能就非常實用,例如:

type Outer = struct {
  inner
  @generated_name("SomeCustomName") struct {};
};

內部結構體可以稱為 SomeCustomNameOuter::Inner

另一個例子是「通訊協定結果」型別:型別 (例如 TicTacToe_MakeMove_Result) 的成功和錯誤變體分別可參照為 TicTacToe_MakeMove_Result::ResponseTicTacToe_MakeMove_Result::Err

通訊協定

假設通訊協定如下:

closed protocol TicTacToe {
    strict StartGame(struct {
        start_first bool;
    });
    strict MakeMove(struct {
        row uint8;
        col uint8;
    }) -> (struct {
        success bool;
        new_state box<GameState>;
    });
    strict -> OnOpponentMove(struct {
        new_state GameState;
    });
};

FIDL 會產生 TicTacToe 類別,做為型別和類別的進入點,用戶端和伺服器都會使用這些型別和類別與這項服務互動。本節其餘部分會以個別小節說明這個類別的成員。

已輸入的管道端點

C++ 繫結會透過 Zircon 通道傳輸,傳送及接收 FIDL 通訊協定訊息,這些訊息會攜帶任意位元組 Blob 和控制代碼。API 會公開三個範本端點類別,而不是公開原始端點 (例如 zx::channel):

  • fidl::ClientEnd<TicTacToe>TicTacToe 通訊協定的用戶端端點,擁有 zx::channel。需要管道專屬擁有權的用戶端繫結會耗用這類繫結。舉例來說,fidl::WireClient<TicTacToe> 可能由 fidl::ClientEnd<TicTacToe> 建構而成,又稱為「將管道繫結至訊息調度器」。
  • fidl::UnownedClientEnd<TicTacToe>:未擁有的值,會借用 TicTacToe 通訊協定的一些用戶端端點。不需要專屬管道擁有權的用戶端 API 會採用這種類型。您可以呼叫 borrow(),從相同通訊協定類型的 ClientEnd 衍生 UnownedClientEnd。借用自端點的物件可能在同一程序中 std::move,但如果仍有未擁有的借用物件,則無法捨棄或移出程序。
  • fidl::ServerEnd<TicTacToe>TicTacToe 通訊協定的伺服器端點,擁有 zx::channel。需要專屬管道擁有權的伺服器繫結會耗用這類資源。舉例來說,您可以提供 fidl::ServerEnd<TicTacToe>fidl::BindServer<TicTacToe>,以及其他參數,建立伺服器繫結。

目前還不需要 UnownedServerEnd,即可安全地導入目前的功能組合。

您可以使用 ::fidl::CreateEndpoints<TicTacToe> 程式庫呼叫建立一對用戶端和伺服器端點。在通訊協定要求管道化情境中,您可以在 std::move() 伺服器端點至遠端伺服器後,立即開始對用戶端端點執行作業。

如要瞭解詳情,請參閱這些類型的類別說明文件。

要求和回應類型

FIDL 方法或事件的要求類型可透過一對別名 (fidl::WireRequestfidl::WireEvent) 存取:

  • fidl::WireRequest<TicTacToe::StartGame>
  • fidl::WireRequest<TicTacToe::MakeMove>
  • fidl::WireEvent<TicTacToe::OnOpponentMove>

如果要求或事件使用的型別是具名型別,別名會指向該型別。如果要求型別是匿名型別,別名會指向為該匿名型別產生的型別名稱。無論是方法要求或事件,產生的要求類型都會命名為 [Method]Request

與要求不同,雙向方法的回應會以新類型 fidl::WireResult 生成:

  • fidl::WireResult<TicTacToe::MakeMove>

fidl::WireResult 型別會從 fidl::Status 繼承,而其狀態會指出呼叫是否在 FIDL 層成功。如果方法有非空白的回應,或使用 FIDL 錯誤語法,產生的 WireResult 型別也會有一組存取子,可存取傳回值或應用程式層錯誤。內含結果可用的存取子如下:

  • WireResultUnwrapType<FidlMethod>* Unwrap()
  • const WireResultUnwrapType<FidlMethod>* Unwrap() const
  • WireResultUnwrapType<FidlMethod>& value()
  • const WireResultUnwrapType<FidlMethod>& value() const
  • WireResultUnwrapType<FidlMethod>* operator->()
  • const WireResultUnwrapType<FidlMethod>* operator->() const
  • WireResultUnwrapType<FidlMethod>& operator*()
  • const WireResultUnwrapType<FidlMethod>& operator*() const

WireResultUnwrapType 是另一種型別別名,取決於方法是否使用錯誤語法。假設有以下範例程式庫:

library response.examples;

protocol Test {
  Foo() -> (struct { x int32; });
  Bar() -> () error int32;
  Baz() -> (struct { x int32; }) error int32;
};

以下是 Test 通訊協定中每個方法的 fidl::WireResultUnwrapType

  • fidl::WireResultUnwrapType<response_examples::Test::Foo> = response_examples::wire::TestFooResponse
  • fidl::WireResultUnwrapType<response_examples::Test::Bar> = fit::result<int32_t>
  • fidl::WireResultUnwrapType<response_examples::Test::Baz> = fit::result<int32_t, ::response_examples::wire::TestBazResponse*>

用戶端

C++ 繫結提供多種方式,可做為用戶端與 FIDL 通訊協定互動:

  • fidl::WireClient<TicTacToe>:這個類別會公開安全無虞的執行緒 API,用於發出非同步和同步呼叫,以及處理非同步事件。擁有管道的用戶端。如要支援非同步 API,以及事件和錯誤處理,就必須使用 async_dispatcher_t*。必須搭配單一執行緒分派器使用。這個類別的物件必須繫結至用戶端端點,並在執行調度器的同一執行緒上終止。在大多數用途中,我們建議使用這個變體,但無法使用 async_dispatcher_t 的情況除外,或是需要將用戶端移至不同執行緒時,也不建議使用這個變體。
  • fidl::WireSharedClient<TicTacToe>:相較於 WireClient,這個類別對執行緒模型較不瞭解,但需要兩階段關閉模式,才能避免使用後釋放。這個類別的物件可能會在任意執行緒上遭到毀損。此外,這個函式庫也支援搭配多執行緒分派器使用。詳情請參閱新版 C++ 繫結執行緒指南
  • fidl::WireSyncClient<TicTacToe>:這個類別會公開純同步 API,用於外撥電話和事件處理。擁有管道的用戶端。
  • fidl::WireCall<TicTacToe>:這個類別與 WireSyncClient 相同, 但沒有管道用戶端結尾的擁有權。 實作採用原始 zx_handle_t 的 C API 時,WireCall 可能比 WireSyncClient 更合適。

WireClient

fidl::WireClient 具有執行緒安全性,支援同步和非同步呼叫,以及非同步事件處理。

創作

用戶端是使用通訊協定的用戶端 fidl::ClientEnd<P>Pasync_dispatcher_t* 建立,以及指向 WireAsyncEventHandler 的選用指標,該指標定義在收到 FIDL 事件或用戶端取消繫結時要呼叫的方法。如果特定事件的虛擬方法未遭到覆寫,系統會忽略該事件。

class EventHandler : public fidl::WireAsyncEventHandler<TicTacToe> {
 public:
  EventHandler() = default;

  void OnOpponentMove(fidl::WireEvent<OnOpponentMove>* event) override {
    /* ... */
  }

  void on_fidl_error(fidl::UnbindInfo unbind_info) override { /* ... */ }
};

fidl::ClientEnd<TicTacToe> client_end = /* logic to connect to the protocol */;
EventHandler event_handler;
fidl::WireClient<TicTacToe> client;
client.Bind(std::move(client_end), dispatcher, &event_handler);

如果伺服器端關閉,或收到來自伺服器的無效訊息,繫結可能會自動終止。您也可以主動終止繫結,方法是銷毀用戶端物件。

外送 FIDL 方法

您可以透過 fidl::WireClient 執行個體叫用外送 FIDL API。取消參照 fidl::WireClient 可存取下列方法:

  • 適用於 StartGame (即發即忘):

    • fidl::Status StartGame(bool start_first):fire and forget 方法的受管理變數。
  • 適用於 MakeMove (雙向):

    • [...] MakeMove(uint8_t row, uint8_t col):非同步雙向方法的受管理變體。這個函式會傳回內部型別,必須用來註冊非同步續傳,以接收結果 (例如回呼)。請參閱指定非同步接續。除非分派器正在關閉,否則後續作業會在分派器執行緒上執行。

fidl::WireClient::buffer 提供下列方法的存取權:

  • fidl::Status StartGame(bool start_first):呼叫端分配的變體,屬於「發送後即忘」方法。
  • [...] MakeMove(uint8_t row, uint8_t col):非同步,由呼叫端分配的雙向方法變體。傳回的內部型別與受管理變數的型別相同。

fidl::WireClient::sync 提供下列方法的存取權:

  • fidl::WireResult<MakeMove> MakeMove(uint8_t row, uint8_t col):雙向方法的同步管理變體。WireSyncClient 上也有相同的方法。
指定非同步接續

請參閱對應的 C++ 說明文件註解

系統會使用結果物件呼叫續傳,該物件代表成功解碼的回應或錯誤。當使用者需要將每個 FIDL 呼叫的錯誤傳播給發起者時,這就非常實用。舉例來說,伺服器在處理現有 FIDL 呼叫時,可能需要發出另一個 FIDL 呼叫,並在發生錯誤時讓原始呼叫失敗。

雙向呼叫傳回的物件有幾種方法:

  • Then:會採用回呼,並在用戶端消失前最多呼叫一次回呼。

  • ThenExactlyOnce:傳遞回呼時,回呼只會執行一次,無論呼叫成功或失敗皆然。不過,由於回呼是以非同步方式叫用,因此請注意在終止用戶端時發生的「使用後釋放」錯誤:回呼擷取的物件可能無效。

  • ThenExactlyOnce 也可能會在需要控管分配時,採用回應內容。TicTacToe 只有一個回應內容 fidl::WireResponseContext<TicTacToe::MakeMove>,其中包含應覆寫的純虛擬方法,用於處理呼叫結果:

virtual void OnResult(fidl::WireUnownedResult<MakeMove>& result) = 0;

系統會使用結果物件呼叫 OnResult,代表成功解碼的回應或錯誤。您有責任確保回應內容物件的存續時間長於整個非同步呼叫的持續時間,因為 fidl::WireClient 會依位址借用內容物件,避免隱含分配。

集中式錯誤處理常式

如果因發生錯誤而拆除繫結,系統會從分派器執行緒叫用 fidl::WireAsyncEventHandler<TicTacToe>::on_fidl_error,並提供詳細原因。如果錯誤是調度器關閉,系統會從呼叫調度器關閉的執行緒叫用 on_fidl_error。建議您將記錄或釋放資源的任何中央邏輯放在該處理常式中。

WireSyncClient

fidl::WireSyncClient<TicTacToe> 是同步用戶端,提供下列方法:

  • explicit WireSyncClient(fidl::ClientEnd<TicTacToe>):建構函式。
  • ~WireSyncClient():預設解構函式。
  • WireSyncClient(&&):預設移動建構函式。
  • WireSyncClient& operator=(WireSyncClient&&):預設移動指派。
  • const fidl::ClientEnd<TicTacToe>& client_end() const:傳回基礎用戶端端點
  • fidl::Status StartGame(bool start_first):fire and forget 方法呼叫的受管理變體。要求的緩衝區配置完全在這個函式中處理。
  • fidl::WireResult<TicTacToe::MakeMove> MakeMove(uint8_t row, uint8_t col): 雙向方法呼叫的管理變數,會將參數當做引數,並傳回 WireResult 物件。要求和回應的緩衝區配置完全由這個函式處理。繫結會在編譯時,根據 FIDL 線路格式和長度上限限制,計算此呼叫專屬的安全緩衝區大小。如果緩衝區小於 512 個位元組,就會在堆疊上分配,否則會在堆積上分配。如要瞭解緩衝區管理詳細資料,請參閱 WireResult
  • fidl::Status HandleOneEvent(SyncEventHandler& event_handler):區塊,可從管道中取用一個事件。如果伺服器已傳送墓誌銘,系統會傳回墓誌銘中包含的狀態。請參閱「活動」。

fidl::WireSyncClient<TicTacToe>::buffer 提供下列方法:

  • fidl::WireUnownedResult<TicTacToe::StartGame> StartGame(bool start_first):呼叫端分配的「即發即忘」呼叫變體,會接收要求緩衝區的備份儲存空間 (做為 buffer 的引數傳遞),以及要求參數,並傳回 fidl::WireUnownedResult

  • fidl::WireUnownedResult<TicTacToe::MakeMove> MakeMove(uint8_t row, uint8_t col):雙向方法的呼叫端分配變體,要求編碼要求空間和接收回應空間,兩者都來自傳遞至 buffer 方法的相同記憶體資源。

請注意,每種方法都有自有和呼叫端分配的變數。簡單來說,每種方法的自有變體都會處理要求和回應的記憶體配置,而呼叫端配置的變體則允許使用者自行提供緩衝區。自有變數較容易使用,但可能會導致額外分配。

WireCall

fidl::WireCall<TicTacToe> 提供與 WireSyncClient 類似的方法,唯一的差異在於 WireCall 可使用 fidl::UnownedClientEnd<TicTacToe> 建構,也就是借用用戶端端點:

  • fidl::WireResult<StartGame> StartGame(bool start_first)StartGame 的自有變體。
  • fidl::WireResult<MakeMove> MakeMove(uint8_t row, uint8_t col)MakeMove 的自有變體。

fidl::WireCall<TicTacToe>(client_end).buffer 提供下列方法:

  • fidl::WireUnownedResult<StartGame> StartGame(bool start_first): 呼叫端分配的 StartGame 變體。
  • fidl::WireUnownedResult<MakeMove> MakeMove(uint8_t row, uint8_t col);: 呼叫端分配的 MakeMove 變體。

Result、WireResult 和 WireUnownedResult

WireSyncClientWireCall 各個方法的受管理變體都會傳回 fidl::WireResult<Method> 型別,而呼叫端分配變體則都會傳回 fidl::WireUnownedResult<Method>fidl::WireClient 上的「即發即忘」方法會傳回 fidl::Status。這些型別定義的方法集相同:

  • zx_status status() const 會傳回傳輸狀態,並在線性化、編碼、對基礎管道進行呼叫,以及解碼結果時 (如適用),傳回遇到的第一個錯誤。如果狀態為 ZX_OK,表示呼叫成功,反之亦然。
  • 如果 status() 不是 ZX_OKfidl::Reason reason() const 會傳回作業失敗的詳細資料。舉例來說,如果編碼失敗,reason() 會傳回 fidl::Reason::kEncodeError。狀態為 ZX_OK 時,不應呼叫 reason()
  • 如果狀態不是 ZX_OKconst char* error_message() const 會包含簡短的錯誤訊息。否則會傳回 nullptr
  • (僅適用於雙向呼叫的 WireResult 和 WireUnownedResult) T* Unwrap() 會傳回回應結構體的指標。如果是 WireResult,指標會指向結果物件擁有的記憶體。對於 WireUnownedResult,指標會指向呼叫端提供的緩衝區。只有在狀態為 ZX_OK 時,才應呼叫 Unwrap()

此外,雙向通話的 WireResultWireUnownedResult 會實作傳回回應結構體的取消參照運算子。這允許以下程式碼:

fidl::WireResult result = client.sync()->MakeMove(0, 0);
auto* response = result.Unwrap();
bool success = response->success;

簡化為:

fidl::WireResult result = client.sync()->MakeMove(0, 0);
bool success = result->success;

WireResult<Method> 會管理所有緩衝區和控制代碼的擁有權,而 ::Unwrap() 則會傳回相關檢視畫面。因此,這個物件的存續時間必須比任何對未封裝回應的參照更長。

分配策略和移動語意

WireResult 會將回應緩衝區內嵌儲存,前提是訊息保證會符合 512 位元組的限制。由於結果物件通常會在呼叫端的堆疊上例項化,這實際上表示回應在合理大小時會進行堆疊分配。如果回應大小上限超過 512 個位元組,WireResult 就會改為包含堆積分配的緩衝區。

因此,WireResult不支援std::move()。如果緩衝區是內嵌,就必須複製內容,且必須將指向行外物件的指標更新至目的地物件內的位置,這對一般認為成本低廉的移動作業來說,是令人意外的額外負擔。

如果結果物件需要在多個函式呼叫之間傳遞,請考慮在最外層的函式中預先配置緩衝區,並使用呼叫端配置的變體。

伺服器

實作 FIDL 通訊協定的伺服器時,需要提供 TicTacToe 的具體實作項目。

產生的 fidl::WireServer<TicTacToe> 類別具有與 FIDL 通訊協定中定義的方法呼叫相應的純虛擬方法。使用者會實作 TicTacToe 伺服器,方法是提供 fidl::WireServer<TicTacToe> 的具體實作,其中包含下列純虛擬方法:

  • virtual void StartGame(StartGameRequestView request, StartGameCompleter::Sync& completer)
  • virtual void MakeMove(MakeMoveRequestView request, MakeMoveCompleter::Sync& completer)

如要瞭解如何繫結及設定伺服器實作,請參閱範例 C++ 伺服器

C++ 連結線繫結也會提供函式,可手動分派訊息 (假設有實作項目 fidl::WireDispatch<TicTacToe>):

  • void fidl::WireDispatch<TicTacToe>(fidl::WireServer<TicTacToe>* impl, fidl::IncomingMessage&& msg, ::fidl::Transaction* txn):調度傳入的訊息。如果沒有相符的處理常式,系統會關閉訊息中的所有控制代碼,並通知 txn 發生錯誤。

要求

系統會將要求做為每個產生的 FIDL 方法處理常式的第一個引數提供。這是要求的檢視畫面 (指標)。所有要求引數都會使用箭頭運算子和引數名稱存取。

例如:

  • request->start_first
  • request->row

如需要求生命週期的附註,請參閱「線路網域物件的記憶體擁有權」。

完成者

在每個產生的 FIDL 方法處理常式中,完成器會做為最後一個引數提供,位於該方法的 FIDL 要求參數之後。完成器類別會擷取完成 FIDL 交易的各種方式,例如傳送回覆、使用墓誌銘關閉管道等,並提供同步和非同步版本 (不過 ::Sync 類別預設會以引數形式提供)。在本範例中,這些完成者為:

  • fidl::WireServer<TicTacToe>::StartGameCompleter::Sync
  • fidl::WireServer<TicTacToe>::StartGameCompleter::Async
  • fidl::WireServer<TicTacToe>::MakeMoveCompleter::Sync
  • fidl::WireServer<TicTacToe>::MakeMoveCompleter::Async

所有完成器類別都提供下列方法:

  • void Close(zx_status_t status):關閉管道並傳送 status 做為墓誌銘。

此外,雙向方法會提供兩個版本的 Reply 方法,用於回覆回應:受管理變體和呼叫端分配變體。這些變體對應於用戶端 API 中的變體。舉例來說,MakeMoveCompleter::SyncMakeMoveCompleter::Async 都提供下列 Reply 方法:

  • ::fidl::Status Reply(bool success, fidl::ObjectView<GameState> new_state)
  • ::fidl::Status Reply(fidl::BufferSpan _buffer, bool success, fidl::ObjectView<GameState> new_state)

由於 Reply 傳回的狀態與取消繫結狀態相同,因此可以放心忽略。

最後,雙向方法的同步完成器可以使用 ToAsync() 方法轉換為非同步完成器。非同步完成器可以超出處理常式的範圍,例如將其移至 Lambda 擷取,讓伺服器非同步回應要求。非同步完成器與同步完成器一樣,都提供回應用戶端的方法。如需使用範例,請參閱「非同步回應要求」。

平行處理訊息

根據預設,系統會依序處理來自單一繫結的訊息,也就是說,如果需要,系統會喚醒附加至調度程式 (執行迴圈) 的單一執行緒、讀取訊息、執行處理常式,然後返回調度程式。::Sync 完成器提供額外的 API EnableNextDispatch(),可用於選擇性地打破這項限制。具體來說,呼叫這個 API 會啟用另一個執行緒,等待分派器處理繫結上的下一則訊息,而第一個執行緒仍在處理常式中。請注意,對相同 Completer 重複呼叫 EnableNextDispatch() 是冪等作業。

void DirectedScan(int16_t heading, ScanForPlanetsCompleter::Sync& completer) override {
  // Suppose directed scans can be done in parallel. It would be suboptimal to block one scan until
  // another has completed.
  completer.EnableNextDispatch();
  fidl::VectorView<Planet> discovered_planets = /* perform a directed planet scan */;
  completer.Reply(std::move(discovered_planets));
}

呼叫端分配的方法

上述許多 API 都提供所擁有和呼叫端分配的產生方法變體。

呼叫端分配的變體會將所有記憶體分配責任延後給呼叫端。fidl::BufferSpan 型別會參照緩衝區位址和大小。繫結程式庫會使用這個緩衝區建構 FIDL 要求,因此緩衝區必須夠大。方法參數 (例如 heading) 會線性化至緩衝區內的適當位置。建立緩衝區的方法有很多種:

// 1. On the stack
using StartGame = TicTacToe::StartGame;
fidl::SyncClientBuffer<StartGame> buffer;
auto result = client.buffer(buffer.view())->StartGame(true);

// 2. On the heap
auto buffer = std::make_unique<fidl::SyncClientBuffer<StartGame>>();
auto result = client.buffer(buffer->view())->StartGame(true);

// 3. Some other means, e.g. thread-local storage
constexpr uint32_t buffer_size = fidl::SyncClientMethodBufferSizeInChannel<StartGame>();
uint8_t* buffer = allocate_buffer_of_size(buffer_size);
fidl::BufferSpan buffer_span(/* data = */buffer, /* capacity = */request_size);
auto result = client.buffer(buffer_span)->StartGame(true);

// Check the transport status (encoding error, channel writing error, etc.)
if (result.status() != ZX_OK) {
  // Handle error...
}

// Don't forget to free the buffer at the end if approach #3 was used...

使用呼叫端分配的風味時,result 物件會借用要求和回應緩衝區 (因此其型別位於 WireUnownedResult 下方)。請確保緩衝區的存留時間比 result 物件長。請參閱「WireUnownedResult」。

活動

在 C++ 繫結中,事件可以非同步或同步處理,具體取決於所用的用戶端類型。

非同步用戶端

使用 fidl::WireClient 時,您可以將 fidl::WireAsyncEventHandler<TicTacToe>* 傳遞至類別,以非同步方式處理事件。WireAsyncEventHandler 類別具有下列成員:

  • virtual void OnOpponentMove(fidl::WireEvent<OnOpponentMove>* event) {}:OnOpponentMove 事件的處理常式 (每個事件一個方法)。

  • virtual on_fidl_error(::fidl::UnbindInfo info) {}:用戶端發生終端錯誤時呼叫的方法。

如要處理事件和錯誤,必須定義從 fidl::WireAsyncEventHandler<TicTacToe> 繼承的類別。

同步處理用戶端

WireSyncClient 中,系統會呼叫 HandleOneEvent 函式並傳遞 fidl::WireSyncEventHandler<TicTacToe>,以同步處理事件。

WireSyncEventHandler 類別包含每個事件的純虛擬方法。在本範例中,這個成員包含下列成員:

  • virtual void OnOpponentMove(fidl::WireEvent<TicTacToe::OnOpponentMove>* event) = 0:OnOpponentMove 事件的控制代碼。

如要處理事件,必須定義從 WireSyncEventHandler 沿用的類別。這個類別必須為通訊協定中的所有事件定義虛擬方法。然後必須建立這個類別的例項。

處理單一事件的方法有兩種。每個事件處理常式都會使用使用者定義的事件處理常式類別執行個體:

  • ::fidl::Status fidl::WireSyncClient<TicTacToe>::HandleOneEvent( SyncEventHandler& event_handler):適用於同步處理用戶端的繫結版本。
  • ::fidl::Status fidl::WireSyncEventHandler<TicTacToe>::HandleOneEvent( fidl::UnownedClientEnd<TicTacToe> client_end):未繫結的版本,使用 fidl::UnownedClientEnd<TicTacToe> 處理特定處理常式的一個事件。

每次呼叫 HandleOneEvent 時,方法都會在管道上等待一則傳入訊息。然後解碼訊息。如果結果為 fidl::Status::Ok(),則表示系統只呼叫一個虛擬方法。否則,系統不會呼叫任何虛擬方法,且狀態會指出錯誤。

如果處理常式一律相同 (從一次呼叫 HandleOneEvent 到另一次),則應建構 WireSyncEventHandler 物件一次,並在每次需要呼叫 HandleOneEvent 時使用。

如果事件標示為暫時性,系統會提供預設實作方式,導致 HandleOneEvent 在收到使用者未處理的暫時性事件時傳回錯誤。

伺服器

fidl::WireSendEvent 用於從伺服器端傳送事件。有兩種多載:

  • fidl::WireSendEvent(const fidl::ServerBindingRef<Protocol>& binding_ref) 透過伺服器繫結參照傳送事件。
  • fidl::WireSendEvent(const fidl::ServerEnd<Protocol>& endpoint) 透過端點傳送事件。
使用伺服器繫結物件傳送事件

將伺服器實作項目繫結至管道時,fidl::BindServer 會傳回 fidl::ServerBindingRef<Protocol>,您可透過這個方式安全地與伺服器繫結互動。

使用繫結參照呼叫 fidl::WireSendEvent 會傳回介面,用於傳送事件。

事件傳送器介面包含傳送各個事件的方法。以具體範例來說,TicTacToe 的事件傳送器介面提供下列方法:

  • fidl::Status OnOpponentMove(GameState new_state):受管理風味。

呼叫 .buffer(...) 會傳回類似的介面,供呼叫端分配風味,並從傳遞至 .buffer 的記憶體資源分配編碼緩衝區,類似於用戶端 API伺服器完成器

使用 ServerEnd 物件傳送事件

伺服器端點本身以 fidl::ServerEnd<Protocol> 表示。

使用伺服器繫結物件傳送事件,是在伺服器端點繫結至實作項目時,傳送事件的標準做法。不過,有時可能需要直接在 fidl::ServerEnd<TicTacToe> 物件上傳送事件,而不必設定伺服器繫結。

fidl::WireSendEvent 會取得 fidl::ServerEnd<Protocol> 的常數參照。 為減少在控點於其他位置關閉後使用端點的機率,這個函式不支援 zx::unowned_channel

結果

假設有下列方法:

protocol TicTacToe {
    MakeMove(struct {
      row uint8;
      col uint8;
    }) -> (struct {
      new_state GameState;
    }) error MoveError;
};

FIDL 會在完成器上產生便利方法,對應於具有錯誤類型的方法。視回覆「變體」而定,完成者將有 ReplySuccessReplyError 或兩種方法,可直接回覆成功或錯誤資料,不必建構聯集。

如果是受管理版本,則可使用這兩種方法:

  • void ReplySuccess(GameState new_state)
  • void ReplyError(MoveError error)

由於 ReplyError 不需要堆積分配,因此只有 ReplySuccess 適用於呼叫端分配的變種版本:

  • void ReplySuccess(fidl::BufferSpan _buffer, GameState new_state)

請注意,傳入的緩衝區用於保存整個回應,而不只是對應成功變體的資料。

您也可以使用定期產生的 Reply 方法:

  • void Reply(TicTacToe_MakeMove_Result result):自有變體。
  • void Reply(fidl::BufferSpan _buffer, TicTacToe_MakeMove_Result result): 由呼叫端分配的變體。

自有和呼叫端分配的變數會使用 TicTacToe_MakeMove_Result 參數,該參數會以兩個變數 (ResponseErr) 的聯集形式產生,其中 ResponseTicTacToe_MakeMove_ResponseErr 則是 MoveErrorTicTacToe_MakeMove_Response 會以 struct 形式產生,並將回應參數做為欄位。在本例中,它具有單一欄位 new_state,也就是 GameState

不明互動處理方式

伺服器端

如果通訊協定宣告為 openajar,產生的 fidl::WireServer<Protocol> 型別也會從 fidl::UnknownMethodHandler<Protocol> 繼承。UnknownMethodHandler 定義伺服器必須實作的單一抽象方法 (稱為 handle_unknown_method),簽章如下:

virtual void handle_unknown_method(UnknownMethodMetadata<Protocol> metadata,
                                   UnknownMethodCompleter::Sync& completer) = 0;

提供的 UnknownMethodMetadata 是結構體,其中包含一或兩個欄位,視通訊協定是 ajaropen 而定。以下是中繼資料結構,為簡化起見,省略了範本引數:

struct UnknownMethodMetadata {
  // Ordinal of the method that was called.
  uint64_t method_ordinal;
  // Whether the method that was called was a one-way method or a two-way
  // method. This field is only defined if the protocol is open, since ajar
  // protocols only handle one-way methods.
  UnknownMethodType unknown_method_type;
};

UnknownMethodType 是具有 kOneWaykTwoWay 兩種變體的列舉,可指出呼叫的方法類型。

UnknownMethodCompleter 與單向方法使用的完成者類型相同。

用戶端

用戶端無法判斷伺服器是否知道 flexible 單向方法。如果是flexible雙向方法,如果伺服器不知道該方法,fidl::WireResultfidl::Status 會是 ZX_ERR_NOT_SUPPORTED,原因為 fidl::Reason::kUnknownMethodkUnknownMethod 原因僅適用於彈性雙向方式。

除了可能在 ReasonkUnknownMethod 中發生錯誤外,用戶端上的 strictflexible 方法之間沒有 API 差異。

如果是 openajar 通訊協定,產生的 fidl::WireAsyncEventHandler<Protocol>fidl::WireSyncEventHandler<Protocol> 會沿用 fidl::UnknownEventHandler<Protocol>UnknownEventHandler 定義事件處理常式必須實作的單一方法 (稱為 handle_unknown_event),其簽章如下:

virtual void handle_unknown_event(UnknownEventMetadata<Protocol> metadata) = 0;

UnknownEventMetadata 的版面配置如下,為求簡單,省略了範本引數:

struct UnknownEventMetadata {
  // Ordinal of the event that was received.
  uint64_t event_ordinal;
};

通訊協定組合

FIDL 沒有繼承的概念,且會為所有組合通訊協定產生如上所述的完整程式碼。換句話說,為

protocol A {
    Foo();
};

protocol B {
    compose A;
    Bar();
};

提供的 API 與為下列項目產生的程式碼相同:

protocol A {
    Foo();
};

protocol B {
    Foo();
    Bar();
};

除了方法序數外,生成的程式碼完全相同。

通訊協定和方法屬性

過渡期

對於以 @transitional 屬性註解的通訊協定方法,通訊協定類別的 virtual 方法會隨附預設的 Close(ZX_NOT_SUPPORTED) 實作方式。這樣一來,即使通訊協定類別的實作項目缺少方法覆寫,也能成功編譯。

可供偵測

使用 @discoverable 屬性註解的通訊協定,會導致 FIDL 工具鍊在通訊協定類別上產生額外的 static const char Name[] 欄位,其中包含完整的通訊協定名稱。

持續性,以及 FIDL 線路格式的獨立使用

目前尚不支援單獨使用 FIDL 線路格式,例如編碼及解碼個別 FIDL 網域物件 (https://fxbug.dev/42163274)。

測試架構

FIDL 工具鍊也會產生名為 wire_test_base.h 的檔案,其中包含用於測試 FIDL 用戶端和伺服器實作的便利程式碼。如要使用這些標頭,請依附於具有 _testing 後置字元 (my_library_cpp_testing 而非 my_library_cpp) 的繫結目標標籤。

伺服器測試基準

測試基準標頭包含每個通訊協定的類別,可為每個類別的方法提供虛設常式實作,因此您只需要實作測試期間使用的方法。這些類別是 fidl::testing::WireTestBase<Protocol>fidl::testing::TestBase<Protocol> 的範本特化,其中 Protocol 是已存根化的 FIDL 通訊協定 (例如,對於通訊協定 games.tictactoe/TicTacToe,測試基準為 fidl::testing::WireTestBase<games_tictactoe::TicTacToe>fidl::testing::TestBase<games_tictactoe::TicTacToe>)。

針對上述列出的相同 TicTacToe 通訊協定,產生的測試基礎子類別為 fidl::WireServer<TicTacToe>fidl::Server<TicTacToe> (請參閱「通訊協定」),提供下列方法:

  • virtual ~WireTestBase() = defaultvirtual ~TestBase() = default: 解構函式。
  • virtual void NotImplemented_(const std::string& name, ::fidl::CompleterBase& completer) = 0:純虛擬方法,會遭到覆寫,用於定義未實作方法的行為。

測試基礎會為虛擬通訊協定方法 StartGameMakeMove 提供實作內容,這些方法會分別實作,以便呼叫 NotImplemented_("StartGame", completer)NotImplemented_("MakeMove", completer)

同步事件處理常式測試基礎

測試基礎標頭包含每個通訊協定的類別,可為每個類別的事件提供 Stub 實作,因此您只需實作測試期間使用的事件。與伺服器測試基準類似,這些類別是 fidl::testing::WireSyncEventHandlerTestBase<Protocol> 的範本特化,其中 Protocol 是存根的 FIDL 通訊協定。

針對上述列出的相同 TicTacToe 通訊協定,系統會產生測試基礎子類別 fidl::WireSyncEventHandler<TicTacToe> (請參閱「通訊協定」),提供下列事件:

  • virtual ~WireSyncEventHandlerTestBase() = default:解構函式。
  • virtual void NotImplemented_(const std::string& name) = 0:純虛擬方法,會遭到覆寫,用於定義未實作事件的行為。

測試基礎會提供虛擬通訊協定事件 OnOpponentMove 的實作項目,該實作項目只會呼叫 NotImplemented_("OnOpponentMove")