新的 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::vmo 或 zx::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 cpp17::optional<FileMode> TryFrom(uint16_t value)
:如果值不含任何未知成員,則根據基礎原始值建構位元的執行個體,否則傳回cpp17::nullopt
。constexpr static FileMode TruncatingUnknown(uint16_t value)
:根據基礎原始值建構位元的執行個體,並清除任何不明的成員。- 位元運算子:提供
|
、|=
、&
、&=
、^
、^=
和~
運算子的實作,允許對mode |= FileMode::kExecute
等位元進行位元操作。 - 比較運算子
==
和!=
。 uint16_t
和bool
的明確轉換函式。
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
結構
如果是 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() const
和bool is_string_value() const
:每個變化版本都有相關聯的方法,可檢查JsonValue
的例項是否屬於該變化版本const int32_t& int_value() const
和const 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>
。這個建構工具需要小心謹慎的記憶體管理,但有時可能很實用。Caveat Emptor。User(User&&)
:預設移動建構函式。~User()
:預設解構器。User& operator=(User&&)
:預設移動指派設定。bool IsEmpty() const
:如果未設定任何欄位,則傳回 true。bool has_age() const
和bool has_name() const
:傳回是否已設定欄位。const uint8_t& age() const
和const 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 {};
};
內部結構可稱為 SomeCustomName
或 Outer::Inner
。
另一個範例是「通訊協定結果」類型:TicTacToe_MakeMove_Result
等類型的成功和錯誤變體可分別以 TicTacToe_MakeMove_Result::Response
和 TicTacToe_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 通訊協定訊息,這種傳輸方式具有任意的位元組和處理常式。針對 zx::channel
執行個體,API 公開了三個範本的端點類別,而不會公開原始端點:
fidl::ClientEnd<TicTacToe>
:TicTacToe
通訊協定的用戶端端點;擁有zx::channel
。需要管道專屬擁有權的用戶端繫結會使用這個類型。例如,fidl::WireClient<TicTacToe>
可以從fidl::ClientEnd<TicTacToe>
建構,也稱為「將管道繫結至訊息調度工具」。fidl::UnownedClientEnd<TicTacToe>
:借用TicTacToe
通訊協定的部分用戶端端點的非擁有值。如果 Client API 不需要管道專屬擁有權,就會採用這種類型。UnownedClientEnd
可以藉由呼叫borrow()
從相同通訊協定類型的ClientEnd
衍生。借用的端點可能會在同一程序內進行std::move
刪除,但由於有非從屬借用,所以不可將其捨棄或轉出至其他程序。fidl::ServerEnd<TicTacToe>
:TicTacToe
通訊協定的伺服器端點;擁有zx::channel
。需要管道專屬擁有權的伺服器繫結會使用這種類型。舉例來說,您可以提供fidl::ServerEnd<TicTacToe>
給fidl::BindServer<TicTacToe>
及其他參數,以建立伺服器繫結。
沒有 UnownedServerEnd
,因為目前還不需要安全實作目前的功能組合。
您可以使用 ::fidl::CreateEndpoints<TicTacToe>
程式庫呼叫建立一對用戶端和伺服器端點。在通訊協定要求管道中,一組可在伺服器端點對遠端伺服器執行 std::move()
後,立即在用戶端端點上執行作業。
詳情請參閱這些類型的課程說明文件。
要求與回應類型
FIDL 方法或事件的要求類型可透過一組別名、fidl::WireRequest
和 fidl::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
具有執行緒安全特性,可支援同步和非同步呼叫,以及非同步事件處理作業。
建立時間
用戶端會建立具有通訊協定 P
的用戶端 fidl::ClientEnd<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)
:Fire 和 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 和 getget 方法呼叫的代管變化版本。系統會透過這個函式完整處理要求的緩衝區分配。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
變數。
結果、WireResult 和 WireUnownedResult
每個 WireSyncClient
和 WireCall
方法的代管變數都會傳回 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()
會傳回回應結構的指標。針對WireResult
,指標會指向結果物件擁有的記憶體。若是WireUnownedResult
,指標會指向呼叫端提供的緩衝區。只有在狀態為ZX_OK
時,才應呼叫Unwrap()
。
此外,雙向呼叫的 WireResult
和 WireUnownedResult
會實作傳回回應結構本身的去參照運算子。這麼做可執行以下程式碼:
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 方法處理常式的最後一個引數。完整類別會擷取完成 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::Sync
和 MakeMoveCompleter::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 擷取),讓伺服器以非同步方式回應要求。Async 完整程序的回應方法與同步完成者相同,可用來回應用戶端。如需使用範例,請參閱以非同步方式回應要求
平行訊息處理
根據預設,系統會依序處理來自單一繫結的訊息,亦即在必要時,啟動附加至調度工具的單一執行緒 (執行迴圈),並讀取訊息、執行處理常式,再傳回調度工具。::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 會在對應含有錯誤類型的方法的完整程序中產生便利方法。視 Reply 的「變化版本」而定,完成者會使用 ReplySuccess
、ReplyError
或兩種方法直接回應成功或錯誤資料,而無須建構聯集。
如果是代管變種版本,這兩種方法都能使用:
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
。
不明互動處理
伺服器端
當通訊協定宣告為 open
或 ajar
時,產生的 fidl::WireServer<Protocol>
類型也會沿用 fidl::UnknownMethodHandler<Protocol>
。UnknownMethodHandler
定義了伺服器必須實作的單一抽象方法 (稱為 handle_unknown_method
),其格式如下:
virtual void handle_unknown_method(UnknownMethodMetadata<Protocol> metadata,
UnknownMethodCompleter::Sync& completer) = 0;
根據通訊協定是 ajar
或 open
,提供的 UnknownMethodMetadata
是一個結構,其中包含一或兩個欄位。為簡單起見,中繼資料結構會如下所示 (省略範本引數):
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
是一個列舉,包含 kOneWay
和 kTwoWay
兩個變化版本,說明呼叫的方法類型。
UnknownMethodCompleter
和單向方法使用的完整類型相同。
用戶端
用戶端無法判斷伺服器是否已得知 flexible
單向方法。針對 flexible
雙向方法,如果伺服器不知道該方法,fidl::WireResult
會具有 ZX_ERR_NOT_SUPPORTED
的 fidl::Status
,原因為 fidl::Reason::kUnknownMethod
。kUnknownMethod
原因僅適用於彈性雙向方法。
除了使用 kUnknownMethod
的 Reason
發生錯誤外,用戶端的 strict
和 flexible
方法之間並無 API 差異。
如果是 open
和 ajar
通訊協定,產生的 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() = default
或virtual ~TestBase() = default
:解構函式。virtual void NotImplemented_(const std::string& name, ::fidl::CompleterBase& completer) = 0
:純虛擬方法遭到覆寫,用於定義未實作方法的行為。
測試基礎為虛擬通訊協定方法 StartGame
和 MakeMove
提供實作,已實作分別僅呼叫 NotImplemented_("StartGame", completer)
和 NotImplemented_("MakeMove",
completer)
。
同步事件處理常式測試基礎
測試基本標頭包含每個通訊協定的類別,可為各個類別的事件提供虛設常式實作,因此您可以只實作測試期間使用的事件。與伺服器測試集類似,這些類別是 fidl::testing::WireSyncEventHandlerTestBase<Protocol>
的範本專業化,其中 Protocol
是虛設常式的 FIDL 通訊協定。
針對上述相同的 TicTacToe
通訊協定,產生 fidl::WireSyncEventHandler<TicTacToe>
的測試基本子類別 (請參閱「通訊協定」),並產生下列事件:
virtual ~WireSyncEventHandlerTestBase() = default
:解構器。virtual void NotImplemented_(const std::string& name) = 0
:純虛擬方法,用於定義未實作事件的行為。
測試基礎為虛擬通訊協定事件 OnOpponentMove
提供實作,此實作僅為呼叫 NotImplemented_("OnOpponentMove")
。