新的 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++ 类型之间的对应关系,请参见 内置类型。字符串被声明为constexpr extern const char[],并在 .cc 文件中定义。

字段

本部分介绍 FIDL 工具链如何将 FIDL 类型转换为 C++ 线路 。这些类型可以在汇总类型中显示为成员或参数 协议方法

内置类型

系统会根据下表将 FIDL 类型转换为 C++ 类型:

FIDL 类型 C++ 线类型
bool bool(requires 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 cpp17::optional<FileMode> TryFrom(uint16_t value):构造 底层基元值的位实例(如果值没有 不包含任何未知成员,否则返回 cpp17::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 的成员相匹配 strict 枚举:

  • 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>):静态 直接构造 union 的特定变体的构造函数。
  • bool has_invalid_tag():如果 JsonValue 的实例返回 true,则返回 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 消息被解码为 JsonValueJsonValue::Which() 将返回 JsonValue::Tag::kUnknown

C++ 绑定绑定不存储未知的原始字节和句柄 变体。

不支持对包含未知变体的并集进行编码,这会导致 编码失败。

表格

根据 table 定义:

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

FIDL 工具链会通过以下方法生成 User 类:

  • User():默认构造函数,用于初始化未设置任何字段的空表。
  • User::Builder(fidl::AnyArena& arena):构建器工厂。 返回一个 fidl::WireTableBuilder<User>,用于分配框架和成员 从提供的 arena 中获取。
  • 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):通过分配来设置名称 从构建器的 arena 中新建 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::Response 的形式引用, TicTacToe_MakeMove_Result::Err

协议

给定 protocol

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 频道的专有所有权将采用此类型。UnownedClientEnd 可通过调用具有相同协议类型的 ClientEnd 派生而来,方法是调用 borrow()。借用端点可以在同一实例内使用 std::move 连接 但无法丢弃或转移到进程外 借用互联网。
  • fidl::ServerEnd<TicTacToe>TicTacToe 协议的服务器端点; 且拥有 zx::channel。需要专有所有权的服务器绑定 渠道将使用这种类型的广告素材例如, 系统可能会向fidl::BindServer<TicTacToe>提供 fidl::ServerEnd<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>:此类针对 外拨的异步和同步调用以及异步事件 处理。它拥有通道的客户端。async_dispatcher_t*是 支持异步 API 以及事件和错误处理。 它必须与单线程调度程序一起使用。此类的对象必须 绑定到客户端端点,并在 运行调度程序。这是大多数应用场景的推荐变体 无法使用 async_dispatcher_t 或当 需要在线程之间移动客户端
  • fidl::WireSharedClient<TicTacToe>:此类对线程处理的看法较少 与 WireClient 相比,但需要两阶段关闭模式 防止释放后使用此类的对象可在 任意线程。它还支持与多线程调度程序一起使用。对于 如需了解详情,请参阅新 C++ 绑定线程指南
  • fidl::WireSyncClient<TicTacToe>:此类公开了纯同步 API 用于去电和事件处理。它拥有 频道
  • fidl::WireCall<TicTacToe>:此类与 WireSyncClient 完全相同 只不过它对渠道的客户端没有所有权。 在实现符合以下条件的 C API 时,WireCall 可能比 WireSyncClient 更可取 获取原始 zx_handle_t

WireClient

fidl::WireClient 是线程安全的,既支持同步,也支持异步 调用以及异步事件处理。

创建

使用客户端 fidl::ClientEnd<P> 根据协议 P 创建客户端, async_dispatcher_t* 以及指向 WireAsyncEventHandler,用于定义 在收到 FIDL 事件或客户端未绑定时调用。如果 virtual 方法未被覆盖,便会忽略相应事件。

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):火灾的调用方分配变体 和 forgot 方法。
  • [...] 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:当传递回调时,该回调将精确执行 一次(在调用成功或失败时)。不过,由于回调 是异步调用的,因此请注意销毁 client:回调捕获的对象 可能无效。

  • 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 和 WireUnownResult

WireSyncClientWireCall 每种方法的受管变体全部 返回 fidl::WireResult<Method> 类型,而调用方分配变体 都会返回一个 fidl::WireUnownedResult<Method>。触发和忘记方法 fidl::WireClient 会返回 fidl::Status。这些类型定义了 方法:

  • zx_status status() const 会返回传输状态。它会返回 在进行线性化、编码、 对底层信道进行调用并对结果进行解码。如果状态为 ZX_OK,表示调用成功,反之亦然。
  • fidl::Reason reason() const 会返回有关哪项操作失败的详细信息, 当 status() 不是 ZX_OK 时。例如,如果编码失败,则 reason() 会返回 fidl::Reason::kEncodeError。不应调用 reason() 当状态为 ZX_OK 时。
  • 当出现以下情况时,const char* error_message() const 会显示简短的错误消息: 状态不是 ZX_OK。否则,返回 nullptr
  • (仅适用于双向通话的 WireResult 和 WireUnownerResult)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

所有 completer 类都提供以下方法:

  • 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 可能 选择性地打破此限制。具体来说,此 API 的调用 将启用另一个等待调度程序处理下一条消息的线程 当第一个线程仍在该 Handler 中时,该对象将被释放。请注意, 对同一 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 会返回一个接口 send 事件。

事件发送方接口包含用于发送每个事件的方法。作为 具体示例,TicTacToe 的事件发送者接口提供 方法:

  • fidl::Status OnOpponentMove(GameState new_state):代管式变种。

调用 .buffer(...) 会返回一个类似的接口来分配调用方 从传递到 Flavor 的内存资源中分配编码缓冲区, .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 将在 completers 上生成便捷方法 与带有错误类型的方法对应。根据回复的“变体” 完成器将具有 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(属于 属于 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 是具有一个或两个字段的结构体 具体取决于协议是 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::WireResultfidl::StatusZX_ERR_NOT_SUPPORTED,原因是 fidl::Reason::kUnknownMethod。通过 kUnknownMethod 原因只能用于灵活的双向方法。

除可能会导致 Reason kUnknownMethodstrictflexible 在 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) 实现。这样就可以实现 Protocol 类缺少方法替换来成功编译。

可检测到

使用 @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 是 是经过存根的(例如,对于协议 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)

同步事件处理程序测试库

测试库标头为每种协议提供了一个类,该类提供了存根 实现每个类的事件,从而能够实现 测试期间使用的事件与服务器测试库类似 这些类是 fidl::testing::WireSyncEventHandlerTestBase<Protocol>,其中 Protocol 是 已存根的 FIDL 协议。

对于上面列出的同一 TicTacToe 协议,生成了测试库子类 fidl::WireSyncEventHandler<TicTacToe>(请参阅协议),提供 以下事件:

  • virtual ~WireSyncEventHandlerTestBase() = default:析构函数。
  • virtual void NotImplemented_(const std::string& name) = 0:纯虚拟 方法。

测试库提供虚拟协议事件的实现 OnOpponentMove:实现为仅调用 NotImplemented_("OnOpponentMove")