New C++ bindings

The new C++ bindings come in natural and wire variants. The natural bindings are optimized for safety and ergonomics, while the wire bindings are optimized for performance. Most code should should use the natural bindings. Only turn to the wire bindings when performance requirements demand it or when you need precise control over memory allocation.

Libraries

Given the library declaration:

library fuchsia.examples;

Protocol types are generated in the fuchsia_examples namespace. Domain objects for this library are generated in the fuchsia_examples::wire namespace, and test scaffolding is generated in the fidl::testing namespace.

Generated type names are transformed to follow the Google C++ style guide.

Constants

Constants are generated as a constexpr. For example, the following constants:

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

Are generated in the header file as:

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

The correspondence between FIDL primitive types and C++ types is outlined in built-in types. Instead of constexpr, strings are declared as an extern const char[] in the header file, and defined in a .cc file.

Fields

This section describes how the FIDL toolchain converts FIDL types to C++ wire types. These types can appear as members in an aggregate type or as parameters to a protocol method.

Built-in types

The FIDL types are converted to C++ types based on the following table:

FIDL Type C++ Wire Type
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 The corresponding zx type is used whenever possible. For example, zx::vmo or zx::channel.

Optional vectors, strings, client/server ends, and handles have the same C++ wire types as their non-optional counterparts.

User defined types

The C++ wire bindings define a class for each user defined bits, enum, struct, table, and union. For strict enums, they define an enum class instead of a regular class. For unions, they use the same class to represent optional and non-optional values. For boxed structs, they use fidl::ObjectView. See Memory ownership of wire domain objects to learn more about fidl::ObjectView.

Type definitions

Bits

Given the bits definition:

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

The FIDL toolchain generates a FileMode class with a static member for each flag, as well as a kMask member that contains a mask of all bits members (in this example 0b111):

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

FileMode provides the following methods:

  • explicit constexpr FileMode(uint16_t): Constructs a value from an underlying primitive value, preserving any unknown bit members.
  • constexpr static cpp17::optional<FileMode> TryFrom(uint16_t value): Constructs an instance of the bits from an underlying primitive value if the value does not contain any unknown members, and returns cpp17::nullopt otherwise.
  • constexpr static FileMode TruncatingUnknown(uint16_t value): Constructs an instance of the bits from an underlying primitive value, clearing any unknown members.
  • Bitwise operators: Implementations for the |, |=, &, &=, ^, ^=, and ~ operators are provided, allowing bitwise operations on the bits like mode |= FileMode::kExecute.
  • Comparison operators == and !=.
  • Explicit conversion functions for uint16_t and bool.

If FileMode is flexible, it will have the following additional methods:

  • constexpr FileMode unknown_bits() const: Returns a bits value that contains only the unknown members from this bits value.
  • constexpr bool has_unknown_bits() const: Returns whether this value contains any unknown bits.

Example usage:

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

Enums

Given the enum definition:

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

The FIDL toolchain generates a C++ enum class using the specified underlying type, or uint32_t if none is specified:

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

Example usage:

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

Flexible enums

Flexible enums are implemented as a class instead of an enum class, with the following methods:

  • constexpr LocationType(): Default constructor, which initializes the enum to an unspecified unknown value.
  • constexpr LocationType(uint32_t value): Explicit constructor that takes in a value of the underlying type of the enum.
  • constexpr bool IsUnknown(): Returns whether the enum value is unknown.
  • constexpr static LocationType Unknown(): Returns an enum value that is guaranteed to be treated as unknown. If the enum has a member annotated with [Unknown], then the value of that member is returned. If there is no such member, then the underlying value of the returned enum member is unspecified.
  • explicit constexpr operator int32_t() const: Converts the enum back to its underlying value.

The generated class contains a static member for each enum member, which are guaranteed to match the members of the enum class in the equivalent strict enum:

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

Structs

Given the struct declaration:

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

The FIDL toolchain generates an equivalent struct:

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

The C++ bindings do not support default values, and instead they zero-initialize all fields of the struct.

Example usage:

// 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");

Unions

Given the union definition:

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

FIDL will generate a JsonValue class. JsonValue contains a public tag enum class representing the possible variants:

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

Each member of Tag has a value matching its ordinal specified in the union definition.

JsonValue provides the following methods:

  • JsonValue(): Default constructor. The constructed union is initially in an "absent" state until a variant is set. The WithFoo constructors should be preferred whenever possible.
  • ~JsonValue(): Destructor that clears the underlying union data.
  • JsonValue(JsonValue&&): Default move constructor.
  • JsonValue& operator=(JsonValue&&): Default move assignment
  • static JsonValue WithIntValue(fidl::ObjectView<int32>) and static JsonValue WithStringValue(fidl::ObjectView<fidl::StringView>): Static constructors that directly construct a specific variant of the union.
  • bool has_invalid_tag(): Returns true if the instance of JsonValue does not yet have a variant set. Calling this method without first setting the variant leads to an assertion error.
  • bool is_int_value() const and bool is_string_value() const: Each variant has an associated method to check whether an instance of JsonValue is of that variant
  • const int32_t& int_value() const and const fidl::StringView& string_value() const: Read-only accessor methods for each variant. Calling these methods without first setting the variant leads to an assertion error.
  • int32_t& int_value() and fidl::StringView& string_value(): Mutable accessor methods for each variant. These methods will fail if JsonValue does not have the specified variant set
  • Tag Which() const: returns the current tag of the JsonValue. Calling this method without first setting the variant leads to an assertion error.

Example usage:

// 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;
}

Flexible unions and unknown variants

Flexible unions have an extra variant in the generated Tag class:

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

When a FIDL message containing a union with an unknown variant is decoded into JsonValue, JsonValue::Which() will return JsonValue::Tag::kUnknown.

The C++ bindings bindings do not store the raw bytes and handles of unknown variants.

Encoding a union with an unknown variant is not supported and will cause an encoding failure.

Tables

Given the table definition:

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

The FIDL toolchain generates a User class with the following methods:

  • User(): Default constructor, initializes an empty table with no fields set.
  • User::Builder(fidl::AnyArena& arena): Builder factory. Returns a fidl::WireTableBuilder<User> that allocates the frame and members from the supplied arena.
  • User::ExternalBuilder(fidl::ObjectView<fidl::WireTableFrame<User>> frame): External builder factory. Returns a fidl::WireTableExternalBuilder<User> with the supplied frame. This builder requires careful, memory management but might occasionally be useful. Caveat Emptor.
  • User(User&&): Default move constructor.
  • ~User(): Default destructor.
  • User& operator=(User&&): Default move assignment.
  • bool IsEmpty() const: Returns true if no fields are set.
  • bool has_age() const and bool has_name() const: Returns whether a field is set.
  • const uint8_t& age() const and const fidl::StringView& name() const: Read-only field accessor methods. Calling these methods without first setting the field leads to an assertion error.

In order to build a table, three additional class is generated: fidl::WireTableBuilder<User>, fidl::WireTableExternalBuilder<User> and fidl::WireTableFrame<User>.

fidl::WireTableFrame<User> is a container for the table's internal storage, and is allocated separately from the builder to maintain the object layout of the underlying wire format. It is only use internally by builders.

fidl::WireTableFrame<User> has the following methods:

  • WireTableFrame(): Default constructor.

fidl::WireTableExternalBuilder<User> has the following methods:

  • fidl::WireTableExternalBuilder<User> age(uint8_t): set age by inlining it into the table frame.
  • fidl::WireTableExternalBuilder<User> name(fidl::ObjectView<fidl::StringView>): set name with an already allocated value.
  • User Build(): build and return the table object. After Build() is called the builder must be discarded.

fidl::WireTableBuilder<User> has all of the methods of fidl::WireTableExternalBuilder<User> (but with the right return type from the setters) and adds:

  • fidl::WireTableBuilder<User> name(std::string_view): set name by allocating a new fidl::StringView from the builder's arena and copying the supplied string into it.

Example usage:

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());

In addition to assigning fields with fidl::ObjectView, you can use any of the allocation strategies described in Memory ownership of wire domain objects.

Inline layouts

The generated C++ code uses the the name chosen by fidlc for inline layouts.

The C++ bindings also generate scoped names to refer to inline layouts. For example, for the FIDL:

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

The inner struct can be referred to using its globally unique name Inner as well as the scoped name Outer::Inner. This can be useful when the top level name is overridden using the @generated_name attribute, for example in:

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

the inner struct can be referred to as SomeCustomName or Outer::Inner.

Another example of this is the protocol result types: the success and error variants of a type such as TicTacToe_MakeMove_Result can be referenced as TicTacToe_MakeMove_Result::Response and TicTacToe_MakeMove_Result::Err, respectively.

Protocols

Given the 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 will generate a TicTacToe class, which acts as an entry point for types and classes that both clients and servers will use to interact with this service. The members of this class are described in individual subsections in the rest of this section.

Typed channel endpoints

The C++ bindings send and receive FIDL protocol messages over the Zircon channel transport, which carry arbitrary blobs of bytes and handles. Rather than exposing raw endpoints, for instance zx::channel, the API exposes three templated endpoint classes:

  • fidl::ClientEnd<TicTacToe>: the client endpoint of the TicTacToe protocol; it owns a zx::channel. Client bindings that require exclusive ownership of the channel would consume this type. For example, a fidl::WireClient<TicTacToe> may be constructed from a fidl::ClientEnd<TicTacToe>, also known as "binding the channel to the message dispatcher".
  • fidl::UnownedClientEnd<TicTacToe>: an unowned value borrowing some client endpoint of the TicTacToe protocol. Client APIs that do not require exclusive ownership of the channel would take this type. An UnownedClientEnd may be derived from a ClientEnd of the same protocol type by calling borrow(). The borrowed-from endpoint may be std::move-ed within the same process, but cannot be dropped or transferred out-of-process, while there are unowned borrows to it.
  • fidl::ServerEnd<TicTacToe>: the server endpoint of the TicTacToe protocol; it owns a zx::channel. Server bindings that require exclusive ownership of the channel would consume this type. For example, a fidl::ServerEnd<TicTacToe> may be provided to fidl::BindServer<TicTacToe> among other parameters to create a server binding.

There is no UnownedServerEnd as it is not yet needed to safely implement the current set of features.

A pair of client and server endpoint may be created using the ::fidl::CreateEndpoints<TicTacToe> library call. In a protocol request pipelining scenario, one can immediately start performing operations on the client endpoint after std::move()-ing the server endpoint to the remote server.

See the class documentation on these types for more details.

Request and response types

The request type for a FIDL method or event can be accessed through a pair of aliases, fidl::WireRequest and fidl::WireEvent:

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

If the type used for the request or event is a named type, the alias will point to that type. If the request type was an anonymous type, the alias will point to the type name generated for that anonymous type. For both method requests and events, the generated request type is named [Method]Request.

Unlike requests, responses to two-way methods are generated as a new type, fidl::WireResult:

  • fidl::WireResult<TicTacToe::MakeMove>

The fidl::WireResult type inherits from fidl::Status, and its status tells whether the call succeeded at the FIDL layer. If the method has a non-empty response or uses FIDL error syntax, the generated WireResult type will also have a set of accessors for accessing the return value or application-layer error. The available accessors for the contained result are:

  • 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

The WireResultUnwrapType is another type alias, which depends on whether the method uses error syntax. Given this example library,

library response.examples;

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

here is what the fidl::WireResultUnwrapType is for each method in the Test protocol:

  • 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*>

Client

The C++ wire bindings bindings provides multiple ways to interact with a FIDL protocol as a client:

  • fidl::WireClient<TicTacToe>: This class exposes thread-safe APIs for outgoing asynchronous and synchronous calls as well as asynchronous event handling. It owns the client end of the channel. An async_dispatcher_t* is required to support the asynchronous APIs as well as event and error handling. It must be used with a single-threaded dispatcher. Objects of this class must be bound to the client endpoint and destroyed on the same thread that is running the dispatcher. This is the recommended variant for most use cases, except for those where an async_dispatcher_t cannot be used or when the client needs to be moved between threads.
  • fidl::WireSharedClient<TicTacToe>: This class has less opinions on threading models compared to WireClient, but requires a two-phase shutdown pattern to prevent use-after-frees. Objects of this class may be destroyed on an arbitrary thread. It also supports use with a multi-threaded dispatcher. For more details, see the New C++ bindings threading guide.
  • fidl::WireSyncClient<TicTacToe>: This class exposes purely synchronous APIs for outgoing calls as well as for event handling. It owns the client end of the channel.
  • fidl::WireCall<TicTacToe>: This class is identical to WireSyncClient except that it does not have ownership of the client end of the channel. WireCall may be preferable to WireSyncClient when implementing C APIs that take raw zx_handle_ts.

WireClient

fidl::WireClient is thread-safe and supports both synchronous and asynchronous calls as well as asynchronous event handling.

Creation

A client is created with a client-end fidl::ClientEnd<P> to the protocol P, an async_dispatcher_t*, and an optional pointer to an WireAsyncEventHandler that defines the methods to be called when a FIDL event is received or when the client is unbound. If the virtual method for a particular event is not overridden, the event is ignored.

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

The binding may be torn down automatically in case of the server-end being closed or due to an invalid message being received from the server. You may also actively tear down the bindings by destroying the client object.

Outgoing FIDL methods

You can invoke outgoing FIDL APIs through the fidl::WireClient instance. Dereferencing a fidl::WireClient provides access to the following methods:

  • For StartGame (fire and forget):

    • fidl::Status StartGame(bool start_first): Managed variant of a fire and forget method.
  • For MakeMove (two way):

    • [...] MakeMove(uint8_t row, uint8_t col): Managed variant of an asynchronous two way method. It returns an internal type that must be used to register the asynchronous continuation for receiving the result, such as a callback. See specifying asynchronous continuation. The continuation will be executed on a dispatcher thread unless the dispatcher is shutting down.

fidl::WireClient::buffer provides access to the following methods:

  • fidl::Status StartGame(bool start_first): Caller-allocated variant of a fire and forget method.
  • [...] MakeMove(uint8_t row, uint8_t col): Asynchronous, caller-allocated variant of a two way method. It returns the same internal type as that from the managed variant.

fidl::WireClient::sync provides access to the following methods:

  • fidl::WireResult<MakeMove> MakeMove(uint8_t row, uint8_t col): Synchronous, managed variant of a two way method. The same method exists on WireSyncClient.
Specifying asynchronous continuation

See the corresponding C++ documentation comments.

The continuation is called with a result object either representing a successfully decoded response or an error. This is useful when the user needs to propagate errors for each FIDL call to their originators. For example, a server may need to make another FIDL call while handling an existing FIDL call, and need to fail the original call in case of errors.

The are a few methods on the returned object from a two way call:

  • Then: takes a callback, and invokes the callback at most once until the client goes away.

  • ThenExactlyOnce: when passed a callback, the callback is executed exactly once, either when the call succeeds or fails. However, because the callbacks are invoked asynchronously, be ware of use-after-free bugs when destroying a client: the objects captured by the callback may not be valid.

  • ThenExactlyOnce may also take a response context when control over allocation is desired. TicTacToe has only one response context, fidl::WireResponseContext<TicTacToe::MakeMove>, which has pure virtual methods that should be overridden to handle the result of the call:

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

OnResult is called with a result object either representing a successfully decoded response or an error. You are responsible for ensuring that the response context object outlives the duration of the entire async call, since the fidl::WireClient borrows the context object by address to avoid implicit allocation.

Centralized error handler

When the binding is torn down due to an error, fidl::WireAsyncEventHandler<TicTacToe>::on_fidl_error will be invoked from the dispatcher thread with the detailed reason. When the error is dispatcher shutdown, on_fidl_error will be invoked from the thread that is calling dispatcher shutdown. It is recommended to put any central logic for logging or releasing resources in that handler.

WireSyncClient

fidl::WireSyncClient<TicTacToe> is a synchronous client which provides the following methods:

  • explicit WireSyncClient(fidl::ClientEnd<TicTacToe>): Constructor.
  • ~WireSyncClient(): Default destructor.
  • WireSyncClient(&&): Default move constructor.
  • WireSyncClient& operator=(WireSyncClient&&): Default move assignment.
  • const fidl::ClientEnd<TicTacToe>& client_end() const: Returns the underlying client endpoint.
  • fidl::Status StartGame(bool start_first): Managed variant of a fire and forget method call. Buffer allocation for requests are entirely handled within this function.
  • fidl::WireResult<TicTacToe::MakeMove> MakeMove(uint8_t row, uint8_t col): Managed variant of a two way method call, which takes the parameters as arguments and returns a WireResult object. Buffer allocation for requests and responses are entirely handled within this function. The bindings calculate a safe buffer size specific to this call at compile time based on FIDL wire-format and maximum length constraints. The buffers are allocated on the stack if they fit under 512 bytes, or else on the heap. See WireResult for details on buffer management.
  • fidl::Status HandleOneEvent(SyncEventHandler& event_handler): Blocks to consume exactly one event from the channel. If the server has sent an epitaph, the status contained within the epitaph is returned. See Events.

fidl::WireSyncClient<TicTacToe>::buffer provides the following methods:

  • fidl::WireUnownedResult<TicTacToe::StartGame> StartGame(bool start_first): Caller-allocated variant of a fire and forget call, which takes in backing storage for the request buffer passed as the argument to buffer, as well as request parameters, and returns an fidl::WireUnownedResult.

  • fidl::WireUnownedResult<TicTacToe::MakeMove> MakeMove(uint8_t row, uint8_t col): Caller-allocated variant of a two way method, which requests both the space for encoding the request and the space for receiving the response from the same memory resource that is passed to the buffer method.

Note that each method has both an owned and caller-allocated variant. In brief, the owned variant of each method handles memory allocation for requests and responses, whereas the caller-allocated variant allows the user to provide the buffer themselves. The owned variant is easier to use, but may result in extra allocation.

WireCall

fidl::WireCall<TicTacToe> provides similar methods to those found in WireSyncClient, with the only difference being that WireCall can be constructed with a fidl::UnownedClientEnd<TicTacToe> i.e. it borrows the client endpoint:

  • fidl::WireResult<StartGame> StartGame(bool start_first): Owned variant of StartGame.
  • fidl::WireResult<MakeMove> MakeMove(uint8_t row, uint8_t col): Owned variant of MakeMove.

fidl::WireCall<TicTacToe>(client_end).buffer provides the following methods:

  • fidl::WireUnownedResult<StartGame> StartGame(bool start_first): Caller-allocated variant of StartGame.
  • fidl::WireUnownedResult<MakeMove> MakeMove(uint8_t row, uint8_t col);: Caller-allocated variant of MakeMove.

Result, WireResult, and WireUnownedResult

The managed variants of each method of WireSyncClient and WireCall all return a fidl::WireResult<Method> type, whereas the caller-allocating variants all return an fidl::WireUnownedResult<Method>. Fire and forget methods on fidl::WireClient return a fidl::Status. These types define the same set of methods:

  • zx_status status() const returns the transport status. it returns the first error encountered during (if applicable) linearizing, encoding, making a call on the underlying channel, and decoding the result. If the status is ZX_OK, the call has succeeded, and vice versa.
  • fidl::Reason reason() const returns details about which operation failed, when status() is not ZX_OK. For example, if encoding failed, reason() will return fidl::Reason::kEncodeError. reason() should not be called when status is ZX_OK.
  • const char* error_message() const contains a brief error message when status is not ZX_OK. Otherwise, returns nullptr.
  • (only for WireResult and WireUnownedResult for two-way calls) T* Unwrap() returns a pointer to the response struct. For WireResult, the pointer points to memory owned by the result object. For WireUnownedResult, the pointer points to the caller-provided buffer. Unwrap() should only be called when the status is ZX_OK.

Additionally, WireResult and WireUnownedResult for two-way calls will implement dereference operators that return the response struct itself. This allows code such as:

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

To be simplified to:

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

WireResult<Method> manages ownership of all buffer and handles, while ::Unwrap() returns a view over it. Therefore, this object must outlive any references to the unwrapped response.

Allocation strategy And move semantics

WireResult stores the response buffer inline if the message is guaranteed to fit under 512 bytes. Since the result object is usually instantiated on the caller's stack, this effectively means the response is stack-allocated when it is reasonably small. If the maximal response size exceeds 512 bytes, WireResult instead contains a heap-allocated buffer.

Therefore, std::move() on WireResult is not supported. The content has to be copied if the buffer is inline, and pointers to out-of-line objects have to be updated to locations within the destination object, these are surprising overheads for a move operation that is commonly understood to be low cost.

If the result object need to be passed around multiple function calls, consider pre-allocating a buffer in the outer-most function and use the caller-allocating flavor.

Server

Implementing a server for a FIDL protocol involves providing a concrete implementation of TicTacToe.

The generated fidl::WireServer<TicTacToe> class has pure virtual methods corresponding to the method calls defined in the FIDL protocol. Users implement a TicTacToe server by providing a concrete implementation of fidl::WireServer<TicTacToe>, which has the following pure virtual methods:

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

Refer to the example C++ server for how to bind and set up a server implementation.

The C++ wire bindings also provide functions for manually dispatching a message given an implementation, fidl::WireDispatch<TicTacToe>:

  • void fidl::WireDispatch<TicTacToe>(fidl::WireServer<TicTacToe>* impl, fidl::IncomingMessage&& msg, ::fidl::Transaction* txn): Dispatches the incoming message. If there is no matching handler, it closes all handles in the message and notifies txn of an error.

Requests

The request is provided as the first argument of each generated FIDL method handler. This a view of the request (a pointer). All the request arguments are accessed using the arrow operator and the argument name.

For example:

  • request->start_first
  • request->row

See Memory ownership of wire domain objects for notes on request lifetime.

Completers

A completer is provided as the last argument of each generated FIDL method handler, after all the FIDL request parameters for that method. The completer classes capture the various ways one can complete a FIDL transaction, e.g. by sending a reply, closing the channel with an epitaph, etc, and come in both synchronous and asynchronous versions (though the ::Sync class is provided as an argument by default). In this example, this completers are:

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

All completer classes provide the following methods:

  • void Close(zx_status_t status): Close the channel and send status as the epitaph.

In addition, two way methods will provide two versions of a Reply method for replying to a response: a managed variant and a caller-allocating variant. These correspond to the variants present in the client API. For example, both MakeMoveCompleter::Sync and MakeMoveCompleter::Async provide the following Reply methods:

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

Because the status returned by Reply is identical to the unbinding status, it can be safely ignored.

Finally, sync completers for two way methods can be converted to an async completer using the ToAsync() method. Async completers can out-live the scope of the handler by e.g. moving it into a lambda capture, allowing the server to respond to requests asynchronously. The async completer has the same methods for responding to the client as the sync completer. See Responding to requests asynchronously for example usage

Parallel message handling

By default, messages from a single binding are handled sequentially, i.e. a single thread attached to the dispatcher (run loop) is woken up if necessary, reads the message, executes the handler, and returns back to the dispatcher. The ::Sync completer provides an additional API, EnableNextDispatch(), which may be used to selectively break this restriction. Specifically, a call to this API will enable another thread waiting on the dispatcher to handle the next message on the binding while the first thread is still in the handler. Note that repeated calls to EnableNextDispatch() on the same Completer are idempotent.

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

Caller-allocated methods

A number of the APIs above provide owned and caller-allocated variants of generated methods.

The caller-allocated variant defers all memory allocation responsibilities to the caller. The type fidl::BufferSpan references a buffer address and size. It will be used by the bindings library to construct the FIDL request, hence it must be sufficiently large. The method parameters (e.g. heading) are linearized to appropriate locations within the buffer. There are a number of ways to create the buffer:

// 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...

When the caller-allocating flavor is used, the result object borrows the request and response buffers (hence its type is under WireUnownedResult). Make sure the buffers outlive the result object. See WireUnownedResult.

Events

In the C++ bindings, events can be handled asynchronously or synchronously, depending on the type of client being used.

Async client

When using a fidl::WireClient, events can be handled asynchronously by passing the class a fidl::WireAsyncEventHandler<TicTacToe>*. The WireAsyncEventHandler class has the following members:

  • virtual void OnOpponentMove(fidl::WireEvent<OnOpponentMove>* event) {}: handler for the OnOpponentMove event (one method per event).

  • virtual on_fidl_error(::fidl::UnbindInfo info) {}: method called when the client encounters a terminal error.

To be able to handle events and errors, a class that inherits from fidl::WireAsyncEventHandler<TicTacToe> must be defined.

Sync client

In WireSyncClient, events are handled synchronously by calling a HandleOneEvent function and passing it a fidl::WireSyncEventHandler<TicTacToe>.

WireSyncEventHandler is a class that contains a pure virtual method for each event. In this example, it consists of the following member:

  • virtual void OnOpponentMove(fidl::WireEvent<TicTacToe::OnOpponentMove>* event) = 0: The handle for the OnOpponentMove event.

To be able to handle events, a class that inherits from WireSyncEventHandler must be defined. This class must define the virtual methods for all the events in the protocol. Then an instance of this class must be created.

There are two ways to handle one event. Each one use an instance of the user defined event handler class:

  • ::fidl::Status fidl::WireSyncClient<TicTacToe>::HandleOneEvent( SyncEventHandler& event_handler): A bound version for sync clients.
  • ::fidl::Status fidl::WireSyncEventHandler<TicTacToe>::HandleOneEvent( fidl::UnownedClientEnd<TicTacToe> client_end): An unbound version that uses an fidl::UnownedClientEnd<TicTacToe> to handle one event for a specific handler.

For each call to HandleOneEvent, the method waits on the channel for exactly one incoming message. Then the message is decoded. If the result is fidl::Status::Ok() then exactly one virtual method has been called. Otherwise, no virtual method has been called and the status indicates the error.

If the handlers are always the same (from one call to HandleOneEvent to the other), the WireSyncEventHandler object should be constructed once and used each time you need to call HandleOneEvent.

If an event is marked as transitional, then a default implementation is provided, which causes HandleOneEvent to return an error upon receiving a transitional event that is not handled by the user.

Server

fidl::WireSendEvent is used to send events from the server side. There are two overloads:

  • fidl::WireSendEvent(const fidl::ServerBindingRef<Protocol>& binding_ref) to send events over a server binding reference.
  • fidl::WireSendEvent(const fidl::ServerEnd<Protocol>& endpoint) to send events over an endpoint.
Sending events using a server binding object

When binding a server implementation to a channel, fidl::BindServer returns a fidl::ServerBindingRef<Protocol>, which is the means by which you may interact safely with a server binding.

Calling fidl::WireSendEvent with a binding reference returns an interface to send events.

The event sender interface contains methods for sending each event. As a concrete example, the event sender interface for TicTacToe provides the following methods:

  • fidl::Status OnOpponentMove(GameState new_state): Managed flavor.

Calling .buffer(...) returns a similar interface for the caller-allocating flavor, allocating encoding buffers from the memory resource passed to .buffer, analogous to the client API as well as the server completers.

Sending events with a ServerEnd object

A server endpoint by itself is represented by fidl::ServerEnd<Protocol>.

Sending events using a server binding object is the standard approach to sending events while the server endpoint is bound to an implementation. However, there may be occasions which call for sending events on a fidl::ServerEnd<TicTacToe> object directly, without setting up a server binding.

fidl::WireSendEvent takes a constant reference to fidl::ServerEnd<Protocol>. It does not support zx::unowned_channel, to reduce the chances of using an endpoint after the handle has been closed elsewhere.

Results

Given a method:

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

FIDL will generate convenience methods on the completers corresponding to methods with an error type. Depending on the Reply "variant", the completer will have ReplySuccess, ReplyError, or both methods to respond directly with the success or error data, without having to construct a union.

For the managed flavor, both methods are available:

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

Since ReplyError doesn't require heap allocation, only ReplySuccess exists for the caller-allocated flavor:

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

Note that the passed in buffer is used to hold the entire response, not just the data corresponding to the success variant.

The regularly generated Reply methods are available as well:

  • void Reply(TicTacToe_MakeMove_Result result): Owned variant.
  • void Reply(fidl::BufferSpan _buffer, TicTacToe_MakeMove_Result result): Caller-allocated variant.

The owned and caller-allocated variant use a parameter of TicTacToe_MakeMove_Result, which is generated as a union with two variants: Response, which is a TicTacToe_MakeMove_Response, and Err, which is a MoveError. TicTacToe_MakeMove_Response is generated as a struct with the response parameters as its fields. In this case, it has a single field new_state, which is a GameState.

Unknown interaction handling

Server-side

When a protocol is declared as open or ajar, the generated fidl::WireServer<Protocol> type will also inherit from fidl::UnknownMethodHandler<Protocol>. The UnknownMethodHandler defines a single abstract method which the server must implement, called handle_unknown_method, with this signature:

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

The provided UnknownMethodMetadata is a struct with one or two fields depending on if the protocol is ajar or open. Here's what the metadata struct looks like, with template arguments omitted for simplicity:

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 is an enum with two variants, kOneWay and kTwoWay, which tells which kind of method was called.

The UnknownMethodCompleter is the same completer type as is used for one-way methods.

Client-side

There is no way for the client to tell if a flexible one-way method was known to the server or not. For flexible two-way methods, if the method is not known to the server the fidl::WireResult will have a fidl::Status of ZX_ERR_NOT_SUPPORTED with a reason of fidl::Reason::kUnknownMethod. The kUnknownMethod reason is only possible for a flexible two-way method.

Aside from the possibility of getting an error with Reason of kUnknownMethod, there are no API differences between strict and flexible methods on the client.

For open and ajar protocols, the generated fidl::WireAsyncEventHandler<Protocol> and fidl::WireSyncEventHandler<Protocol> will inherit from fidl::UnknownEventHandler<Protocol>. UnknownEventHandler defines a single method which event handlers must implement, called handle_unknown_event, with this signature:

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

The UnknownEventMetadata has this layout, with template arguments omitted for simplicity:

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

Protocol composition

FIDL does not have a concept of inheritance, and generates full code as described above for all composed protocols. In other words, the code generated for

protocol A {
    Foo();
};

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

Provides the same API as the code generated for:

protocol A {
    Foo();
};

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

The generated code is identical except for the method ordinals.

Protocol and method attributes

Transitional

For protocol methods annotated with the @transitional attribute, the virtual methods on the protocol class come with a default Close(ZX_NOT_SUPPORTED) implementation. This allows implementations of the protocol class with missing method overrides to compile successfully.

Discoverable

A protocol annotated with the @discoverable attribute causes the FIDL toolchain to generate an additional static const char Name[] field on the protocol class, containing the full protocol name.

Persistence, and standalone use of the FIDL wire format

Standalone use of the FIDL wire format, such as encoding and decoding individual FIDL domain objects, are not yet supported (https://fxbug.dev/42163274).

Test scaffolding

The FIDL toolchain also generates a file named wire_test_base.h that contains convenience code for testing FIDL client and server implementations. To use these headers, depend on the bindings target label with a _testing suffix (my_library_cpp_testing instead of my_library_cpp).

Server test base

The test base header contains a class for each protocol that provides stub implementations for each of the class’ methods, making it possible to implement only the methods that are used during testing. These classes are template specializations of fidl::testing::WireTestBase<Protocol> or fidl::testing::TestBase<Protocol> where Protocol is the FIDL protocol that is stubbed (e.g. for protocol games.tictactoe/TicTacToe, the test bases are fidl::testing::WireTestBase<games_tictactoe::TicTacToe> or fidl::testing::TestBase<games_tictactoe::TicTacToe>).

For the same TicTacToe protocol listed above, generated test base subclasses fidl::WireServer<TicTacToe> and fidl::Server<TicTacToe> (see Protocols), offering the following methods:

  • virtual ~WireTestBase() = default or virtual ~TestBase() = default : Destructor.
  • virtual void NotImplemented_(const std::string& name, ::fidl::CompleterBase& completer) = 0: Pure virtual method that is overridden to define behavior for unimplemented methods.

The test base provides an implementation for the virtual protocol methods StartGame and MakeMove, which are implemented to just call NotImplemented_("StartGame", completer) and NotImplemented_("MakeMove", completer), respectively.

Synchronous event handler test base

The test base header contains a class for each protocol that provides stub implementations for each of the class’ events, making it possible to implement only the events that are used during testing. Similar to the server test base, these classes are template specializations of fidl::testing::WireSyncEventHandlerTestBase<Protocol> where Protocol is the FIDL protocol that is stubbed.

For the same TicTacToe protocol listed above, generated test base subclasses fidl::WireSyncEventHandler<TicTacToe> (see Protocols), offering the following events:

  • virtual ~WireSyncEventHandlerTestBase() = default: Destructor.
  • virtual void NotImplemented_(const std::string& name) = 0: Pure virtual method that is overridden to define behavior for unimplemented events.

The test base provides an implementation for the virtual protocol events OnOpponentMove, which is implemented to just call NotImplemented_("OnOpponentMove").