新增 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[],而非 constexpr,並在 .cc 檔案中定義。

欄位

本節將說明 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,請參閱「Wire 網域物件的記憶體擁有權」。

類型定義

位元

假設 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(),
  };

當含有不明變化的 union 的 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>。這個建構工具需要小心管理記憶體,但偶爾可能會很實用。Caveat Emptor
  • 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> 的所有方法 (但使用 setter 的正確傳回類型),並新增下列項目:

  • 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。除了公開原始端點 (例如 zx::channel) 之外,API 還會公開三個範本端點類別:

  • fidl::ClientEnd<TicTacToe>TicTacToe 通訊協定的用戶端端點,擁有 zx::channel。需要管道專屬擁有權的用戶端繫結會使用這類型別。舉例來說,fidl::WireClient<TicTacToe> 可能會從 fidl::ClientEnd<TicTacToe> 建構,這也稱為「將管道繫結至訊息調度器」。
  • fidl::UnownedClientEnd<TicTacToe>:未擁有的值,借用 TicTacToe 通訊協定的部分用戶端端點。不需管道專屬擁有權的用戶端 API 會採用這類型別。UnownedClientEnd 可以透過呼叫 borrow(),從相同通訊協定類型的 ClientEnd 衍生。從中借用的端點可以在同一個程序中 std::move,但如果有未擁有的借用,則無法捨棄或轉移至程序外。
  • fidl::ServerEnd<TicTacToe>TicTacToe 通訊協定的伺服器端點,擁有 zx::channel。需要管道專屬擁有權的伺服器繫結會使用這類型。舉例來說,您可以將 fidl::ServerEnd<TicTacToe> 提供給 fidl::BindServer<TicTacToe> 和其他參數,以建立伺服器繫結。

由於目前的功能組合尚未需要 UnownedServerEnd,因此沒有 UnownedServerEnd

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

如需更多詳細資訊,請參閱這些類型的類別說明文件。

要求和回應類型

您可以透過一組別名 fidl::WireRequestfidl::WireEvent 存取 FIDL 方法或事件的要求類型:

  • 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,用於傳出非同步和同步呼叫,以及非同步事件處理。它擁有管道的用戶端。async_dispatcher_t* 必須支援非同步 API 以及事件和錯誤處理。必須搭配單執行緒調度器使用。此類別的物件必須繫結至用戶端端點,並在執行調度器的同一執行緒中銷毀。這是大多數用途的建議變化版本,但在無法使用 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> 建立至通訊協定 P、一個 async_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):啟動並遺忘方法的受管理變化版本。
  • 適用於 MakeMove (雙向):

    • [...] MakeMove(uint8_t row, uint8_t col):非同步雙向方法的受管理變化版本。它會傳回內部類型,必須用來註冊非同步接續,以便接收結果,例如回呼。請參閱「指定非同步接續」一文。除非調度器關閉,否則會在調度器執行緒上執行繼續作業。

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

  • fidl::Status StartGame(bool start_first):呼叫端為 fire and forget 方法分配的變化版本。
  • [...] 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):管理的「發送並遺忘」方法呼叫變化版本。要求的緩衝區配置會完全在這個函式中處理。
  • 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_OK 時,fidl::Reason reason() const 會傳回失敗作業的詳細資料。舉例來說,如果編碼失敗,reason() 會傳回 fidl::Reason::kEncodeError。狀態為 ZX_OK 時,不應呼叫 reason()
  • 當狀態不是 ZX_OK 時,const char* error_message() const 會包含簡短的錯誤訊息。否則會傳回 nullptr
  • (僅適用於雙向呼叫的 WireResult 和 WireUnownedResult) T* Unwrap() 會傳回 response struct 的指標。對於 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() 則會傳回其檢視畫面。因此,這個物件必須比任何對解開的回應參照更長壽。

分配策略和移動語意

如果訊息保證可容納在 512 個位元組以下,WireResult 會在內文中儲存回應緩衝區。由於結果物件通常會在呼叫端堆疊上進行例項化,因此這實際上表示在回應相當小時,回應會以堆疊方式進行配置。如果回應大小上限超過 512 個位元組,WireResult 就會改為包含堆積分配緩衝區。

因此,系統不支援在 WireResult 上使用 std::move()。如果緩衝區為內嵌式,則必須複製內容,而指向離線物件的指標也必須更新至目的地物件內的位置,這些都是一般認為成本較低的移動作業所產生的意外額外負擔。

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

伺服器

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

產生的 fidl::WireServer<TicTacToe> 類別具有純虛擬方法,對應於 FIDL 通訊協定中定義的方法呼叫。使用者可透過提供 fidl::WireServer<TicTacToe> 的具體實作來實作 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 要求參數之後。completer 類別可擷取完成 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 呼叫到另一個 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伺服器 completer

使用 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 參數,該參數會產生為具有兩個變化版本的聯集Response (TicTacToe_MakeMove_Response) 和 Err (MoveError)。TicTacToe_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 是一個結構體,其中包含一或兩個欄位,取決於通訊協定是 ajar 還是 open。以下是中繼資料結構體的樣貌,為簡化說明,我們省略了範本引數:

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::WireResult 就會具有 ZX_ERR_NOT_SUPPORTEDfidl::Status,且原因為 fidl::Reason::kUnknownMethodkUnknownMethod 原因僅適用於彈性雙向方法。

除了 kUnknownMethodReason 可能會發生錯誤之外,用戶端的 strictflexible 方法之間沒有任何 API 差異。

對於 openajar 通訊協定,系統會從 fidl::UnknownEventHandler<Protocol> 繼承產生的 fidl::WireAsyncEventHandler<Protocol>fidl::WireSyncEventHandler<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)。

伺服器測試基地

測試基礎標頭包含每個通訊協定的類別,為每個類別的方法提供 Stub 實作,因此只會實作測試期間使用的相關方法。這些類別是 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")