Comparing new C++ and high-level C++ language bindings

Quick reference

Here's how to recognize if a particular type/function/identifier in C++ code is part of the new C++ bindings or high-level C++ bindings.

Taking the examples.keyvaluestore.baseline library as example:

library examples.keyvaluestore.baseline;

type Item = struct {
    key string:128;
    value vector<byte>:64000;
};

type WriteError = flexible enum {
    UNKNOWN = 0;
    INVALID_KEY = 1;
    INVALID_VALUE = 2;
    ALREADY_EXISTS = 3;
};

@discoverable
open protocol Store {
    /// Writes an item to the store.
    flexible WriteItem(struct {
        attempt Item;
    }) -> () error WriteError;
};

Here are how the various FIDL elements will map to in the C++ bindings. Note that in the table "C++" refers to the new C++ bindings, and applies equally to both natural domain objects and wire domain objects. "Natural" refers to the natural domain objects in the new C++ bindings. "Wire" refers to the wire domain objects in the new C++ bindings.

FIDL element C++ natural types Comments
Header include C++ #include <fidl/examples.keyvaluestore.baseline/cpp/fidl.h> Format is `fidl/library name/cpp/fidl.h`
HLCPP #include <examples/keyvaluestore/baseline/cpp/fidl.h> Format is `slash separated library name/cpp/fidl.h`
The library C++ ::examples_keyvaluestore_baseline New C++ uses a single level namespace.
HLCPP uses nested namespaces.
HLCPP ::examples::keyvaluestore::baseline
Item struct Natural ::examples_keyvaluestore_baseline::Item On top of the namespace differences, the wire types are nested under "::wire".
Wire ::examples_keyvaluestore_baseline::wire::Item
HLCPP ::examples::keyvaluestore::baseline::Item
WriteError enum Natural ::examples_keyvaluestore_baseline::WriteError On top of the namespace differences, the wire types are nested under "::wire".
In case of enums and bits, the wire types and natural types are equivalent. There is just an extra type alias.
Wire ::examples_keyvaluestore_baseline::wire::WriteError
HLCPP ::examples::keyvaluestore::baseline::WriteError
string:128 Natural std::string
Wire fidl::StringView
HLCPP std::string
vector<byte>:64000 Natural std::vector<uint8_t>
Wire fidl::VectorView<uint8_t>
HLCPP std::vector<uint8_t>
protocol Store C++ ::examples_keyvaluestore_baseline::Store A marker type that carries some information about the protocol
HLCPP ::examples::keyvaluestore::baseline::Store An abstract base class that contains methods in the protocol
client_end:Store C++ fidl::ClientEnd<Store>
HLCPP fidl::InterfaceHandle<Store>
server_end:Store C++ fidl::ServerEnd<Store>
HLCPP fidl::InterfaceRequest<Store>
Client and server types
for the Store protocol
Natural Client: fidl::Client<Store>
Synchronous client: fidl::SyncClient<Store>
Server interface: fidl::Server<Store>
Event handler interface: fidl::EventHandler<Store>
Wire Client: fidl::WireClient<Store>
Synchronous client: fidl::WireSyncClient<Store>
Server interface: fidl::WireServer<Store>
Event handler interface: fidl::WireEventHandler<Store>
HLCPP Client: fidl::InterfacePtr<Store>
Synchronous client: fidl::SynchronousInterfacePtr<Store>
Server interface: Store
Event handler interface: N/A. InterfacePtr has setters that take one callback per event declaration.

Here's the most common way to set up a client:

C++ (Natural)

  // Connect to the protocol inside the component's namespace. This can fail so it's wrapped in a
  // |zx::result| and it must be checked for errors.
  zx::result client_end = component::Connect<examples_canvas_baseline::Instance>();
  if (!client_end.is_ok()) {
    FX_LOGS(ERROR) << "Synchronous error when connecting to the |Instance| protocol: "
                   << client_end.status_string();
    return -1;
  }

  // Create an instance of the event handler.
  EventHandler event_handler(loop);

  // Create an asynchronous client using the newly-established connection.
  fidl::Client client(std::move(*client_end), dispatcher, &event_handler);
  FX_LOGS(INFO) << "Outgoing connection enabled";

C++ (Wire)

  // Connect to the protocol inside the component's namespace. This can fail so it's wrapped in a
  // |zx::result| and it must be checked for errors.
  zx::result client_end = component::Connect<examples_canvas_baseline::Instance>();
  if (!client_end.is_ok()) {
    FX_LOGS(ERROR) << "Synchronous error when connecting to the |Instance| protocol: "
                   << client_end.status_string();
    return -1;
  }

  // Create an instance of the event handler.
  EventHandler event_handler(loop);

  // Create an asynchronous client using the newly-established connection.
  fidl::WireClient client(std::move(*client_end), dispatcher, &event_handler);
  FX_LOGS(INFO) << "Outgoing connection enabled";

HLCPP

  // Connect to the protocol inside the component's namespace, then create an asynchronous client
  // using the newly-established connection.
  examples::canvas::baseline::InstancePtr instance_proxy;
  auto context = sys::ComponentContext::Create();
  context->svc()->Connect(instance_proxy.NewRequest(dispatcher));
  FX_LOGS(INFO) << "Outgoing connection enabled";

  instance_proxy.set_error_handler([&loop](zx_status_t status) {
    FX_LOGS(ERROR) << "Shutdown unexpectedly";
    loop.Quit();
  });

See the canvas example for the full code listing and explanation.

Here's the most common way to implement a server:

C++ (Natural)

// An implementation of the |Instance| protocol.
class InstanceImpl final : public fidl::Server<examples_canvas_baseline::Instance> {
  void AddLine(AddLineRequest& request, AddLineCompleter::Sync& completer) override {
    // ...
  }
};

C++ (Wire)

// An implementation of the |Instance| protocol.
class InstanceImpl final : public fidl::WireServer<examples_canvas_baseline::Instance> {
  void AddLine(AddLineRequestView request, AddLineCompleter::Sync& completer) override {
    // ...
  }
};

HLCPP

// An implementation of the |Instance| protocol.
class InstanceImpl final : public examples::canvas::baseline::Instance {
  void AddLine(Line line) override {
    // ...
  }
};

See the canvas example for the full code listing and explanation.

New C++ bindings

The new C++ bindings supports both low-level and high-level use cases, by offering two families of generated domain objects, and corresponding client and server APIs that speak those types.

Natural types

  • Optimized to meet the needs of high-level service programming.
  • Represent data structures using idiomatic C++ types such as std::vector, std::optional, and std::string.
  • Use smart pointers to manage heap allocated objects.
  • Use zx::handle to manage handle ownership.
  • Can convert data between their wire (e.g. fidl::StringView) and natural type representations (e.g. std::string).

Wire types

  • Optimized to meet the needs of low-level systems programming while providing slightly more safety and features than the C bindings.
  • Represent data structures whose memory layout coincides with the wire format, i.e. satisfying C++ Standard Layout. This opens the door to in-place encoding and decoding.
  • Generated structures are views of an underlying buffer; they do not own memory.
  • Support in-place access of FIDL messages.
  • Provide fine-grained control over memory allocation.
  • Use owned handle types such as zx::handle. Note that since generated structures are views of an underlying buffer, a parent structure will only own child handles if it also owns their underlying buffer. For example, a FIDL struct owns all the handles stored inline, but a FIDL vector of structs containing handles will be represented as a vector view, which will not own the out-of-line handles.

Client and server APIs

  • Code generator produces more code compared to the C bindings. This includes constructors, destructors, copy/move functions, conversions between domain object families, protocol client implementations, and pure virtual server interfaces.
  • Users implement a server by sub-classing a provided server interface and overriding the pure virtual methods for each operation.
  • Clients supporting sync and async calls, and sync and async event handling.
  • Requires C++17 or above.

Refer to the New C++ tutorial to get started.

High-level C++ bindings

  • Optimized to meet the needs of high-level service programming.
  • Represent data structures using idiomatic C++ types such as std::vector, std::optional, and std::string.
  • Use smart pointers to manage heap allocated objects.
  • Use zx::handle (libzx) to manage handle ownership.
  • Can convert data from in-place FIDL buffers to idiomatic heap allocated objects.
  • Can convert data from idiomatic heap allocated objects (e.g. std::string) to in-place buffers (e.g. as a fidl::StringView).
  • Code generator produces more code compared to the C bindings. This includes constructors, destructors, protocol proxies, protocol stubs, copy/move functions, and conversions to/from in-place buffers.
  • Client performs protocol dispatch by sub-classing a provided stub and implementing the virtual methods for each operation.
  • Both async and synchronous clients are supported. However, the async clients are not thread-safe.
  • Requires C++14 or above.

Refer to the HLCPP tutorial to get started.

Summary

Category New C++ with wire types New C++ with natural types High-level C++
audience drivers and performance-critical applications high-level services high-level services
abstraction overhead RAII closing of handles 1 heap allocation, construction, destruction heap allocation, construction, destruction
type safe types enums, structs, unions, handles, protocols enums, structs, unions, handles, protocols enums, structs, unions, handles, protocols
storage stack, user-provided buffer, or heap heap heap
lifecycle manual or automatic free automatic free (RAII) automatic free (RAII)
receive behavior decode in-place decode into heap decode then move to heap
send behavior copy or vectorize copy copy
calling protocol methods free functions or proxy free functions or proxy call through proxies, register callbacks
implementing protocol methods manual dispatch or implement stub interface implement stub interface implement stub object, invoke callbacks
async client yes yes yes
async server yes (unbounded) 2 yes (unbounded) [^2] yes (unbounded)
parallel server dispatch yes 3 yes [^3] no
generated code footprint large large large

  1. Generated types own all handles stored inline. Out-of-line handles e.g. those behind a pointer indirection are not closed when the containing object of the pointer goes away. In those cases, the bindings provide a fidl::DecodedValue object to manage all handles associated with a call. 

  2. The bindings library defined in lib/fidl can dispatch an unbounded number of in-flight transactions via fidl::BindServer defined in lib/fidl/cpp/wire/channel.h

  3. The bindings library lib/fidl enables parallel dispatch using the EnableNextDispatch() API defined in lib/fidl/cpp/wire/async_transaction.h