blob: 5ab84520a6b6040118bf762907492c62e8367e72 [file] [log] [blame] [view]
# New C++ bindings tutorials
This section helps you learn how to use the new C++ FIDL bindings. See
[Getting started](#getting_started) for a step-by-step guide to setting up the
build and writing a simple client or server from scratch. See [Topics](#topics)
for more involved guides and recommendations to using the bindings effectively.
See [Terminologies](#terminologies) for names and concepts that come up
frequently in code, and a quick explainer on how to make the right choices.
At a high level, the C++ bindings is made up of:
* **Data**: the [domain objects](/docs/glossary/README.md#domain-object) (the
generated FIDL structures e.g. struct, table, ...)
* **Behavior**: the client/server APIs to send those domain objects over a
protocol, receive events, ...
## Natural and wire domain objects
The bindings support two types of domain objects: **natural** types and
**wire** types:
* **natural** types are high level domain objects that optimize for ergonomics.
* These types own their children with smart pointers.
* They use idiomatic C++ types such as `std::vector`, `std::optional`, and
`std::string`.
* Given a FIDL library named `fuchsia.my.lib`, the types are generated
in the `fuchsia_my_lib` namespace.
* **wire** types are optimized for performance and in-place decoding.
* They are specialized C++ Standard Layout types whose memory layout
coincides with the FIDL wire format.
* Out-of-line children are unowned pointers into a separate buffer.
See [Memory ownership of wire domain objects][memory-ownership].
* Given a FIDL library named `fuchsia.my.lib`, the types are generated
in the `fuchsia_my_lib::wire` namespace.
When starting a project, choose natural types by default, since they are easier
to use and reasonably performant. Only turn to the wire types when optimizing
logic in the critical path, or when needing to precisely control memory
allocation. Because the wire types consists of unsafe views, improper use of
those may lead to use-after-free and other memory safety bugs.
## Getting started
1. [Using natural and wire domain objects][domain-objects]
2. [Write a server][server]
3. Write a client ([async][async] or [synchronous][sync])
## Topics
* [Memory ownership of wire domain objects][memory-ownership]
* [Responding asynchronously][async-completers]
* [Thread safety and memory safety][threading]
* [Communicating over the driver transport][driver-transport]
* [Request pipelining][pipelining]
* [Unified services][services]
## Terminologies
### `Wire` vs no `Wire` in clients and servers
The presence of `Wire` prefix in a client/server API name indicates that the API
only accepts wire types. Otherwise, the API typically can accept both wire and
natural types, or defaults to natural types. For example, `fidl::Client`
supports making calls with both natural and wire types, whereas
`fidl::WireClient` exposes a more restrictive interface that only accepts wire
types.
`fidl::Server` receives requests into natural types, and may send replies with
either natural or wire types. On the other hand, `fidl::WireServer` receives
requests into wire types and sends replies exclusively in wire types.
To support both wire and natural types on the send side without function
overload ambiguities, the wire interfaces are housed under a `.wire()` accessor.
For example, given a `fidl::Client<MyProtocol> client;`, one would write
`client->SomeMethod(natural_type);` to make request using natural types, and
`client.wire()->SomeMethod(wire_type);` to make request using wire types.
#### Recommendation
Use client/server APIs without the `Wire` prefix. Only when there is a need to
ensure at compile time that only wire types are used, one may define function
signatures that use the `Wire` counterparts e.g. `fidl::WireClient`. One may
also depend on only the wire parts of the bindings by depending on the
`fuchsia.my.lib_cpp_wire` target instead of the `fuchsia.my.lib_cpp` GN target.
<!-- TODO(https://fxbug.dev/42181382): Talk about Bazel targets -->
Note: the wire types portions of the bindings was historically named "Low-Level
C++ bindings", or LLCPP.
### `Sync` vs no `sync` in clients
Synchronous, or "sync" for short, applies to FIDL calls with a response (two-way
calls), and means the call is blocking: a thread making such a call will not
return from the call until the response comes back. For example,
`fidl::WireSyncClient` is a client where all two-way calls are synchronous.
Similarly, `fidl::WireClient` has a `.sync()` accessor which returns an
interface for making synchronous calls.
One-way calls do not have a response, hence the concept of synchronousness do
not apply to them.
#### Recommendation
If your code is a standalone program that only consumes capabilities from other
components, determine the level of concurrency required by its business needs:
* If it does not manage lots of concurrent operations, you may use a synchronous
client which leads to easy to read straight-line logic. For example, a
short-running command line tool may use `fidl::SyncClient`.
* If your code manages lots of concurrent operations, it typically has access to
an asynchronous dispatcher (`async_dispatcher_t*`). When choosing between
synchronous and asynchronous clients and calls in that case, prefer the
asynchronous counterpart. For example, prefer `fidl::WireClient` without going
through `.sync()` over `fidl::WireSyncClient` or `.sync()`. In particular, do
not make synchronous calls on a dispatcher thread if the dispatcher is single
threaded, to avoid deadlocks.
If your code is a service, i.e. a component that provides capabilities to other
components, it should use asynchronous dispatchers and asynchronous clients to
support the level of concurrency needed by multiple consumers.
If your code is a library that's used by other applications, it will require
more careful thought regarding whether it should expose a synchronous or
asynchronous interface, depending on the needs of its users. For example, a
library using synchronous clients and exposing a synchronous interface will be
more difficult to use by highly concurrent applications that schedules their
work on asynchronous dispatchers.
The above is general advice, and different asynchronous runtimes may have their
own more specific recommendations.
### `Shared` vs no `shared` in clients
When a client type has "shared" in its name, it may be bound and destroyed on
arbitrary threads. See [`SharedClient`][shared-client] in the threading
guide. It will have a counterpart without "shared", such as `Client`, that
must be bound and destroyed on the dispatcher thread. A similar relationship
exists between `WireClient` and `WireSharedClient`.
#### Recommendation
When choosing between [`Client`][client] and `SharedClient`, prefer
`Client` unless the threading model or performance requirements of your
application necessitates multi-threaded usage of clients. Refer to the
[threading guide][threading] for the many areas of caution when using
`SharedClient`. The extra restrictions in `Client` are designed to
reduce memory races and use-after-frees. For example, you may use `Client`
if your objects all live on the same single-threaded async dispatcher.
### `Then` vs `ThenExactlyOnce` in two-way calls
When an asynchronous call has a response, there are two ways to specify a
callback to receive the result of that call:
* When you use `.ThenExactlyOnce(...)`, the callback is always called exactly
once, delivering the result.
* When you use `.Then(...)`, the callback is silently discarded when the client
object is destroyed, which is suitable for object-oriented code.
#### Motivation for `Then`
When making an asynchronous two-way call, the result of that call is delivered
back to the application at a later time, after the execution had already left
the original scope of making the call. The asynchronous dispatcher would later
invoke the follow-up logic you specified when making the call, called a
_continuation_. This means it's easy to use objects after they are destroyed,
leading to memory corruptions:
```c++
// The following snippet shows an example of use-after-free
// occurring in asynchronous two-way calls.
void Foo(fidl::WireClient<MyProtocol>& client) {
bool call_ok;
client->SomeMethod().Then(
// The following lambda function represents the continuation.
[&call_ok] (fidl::WireUnownedResult<SomeMethod>& result) {
// `call_ok` has already gone out of scope.
// This would lead to memory corruption.
call_ok = result.ok();
});
}
```
A more insidious form of this corruption occurs when the continuation captures
the `this` pointer, and said referenced object also owns the client. Destroying
the outer object (in turn, destroying the client) causes all pending two-way
calls to fail. As their continuation runs, the `this` pointer it captured is no
longer valid.
Both `Then` and `ThenExactlyOnce` registers a continuation for a two-way call.
However, `Then` is designed to mitigate corruption cases like the above.
Specifically:
* `Then` ensures the provided continuation will be called at most once, until
the client is destroyed. You should choose `Then` if your continuation only
captures objects with the same lifetime as the client (e.g. your user object
owns the client). Destroying the user object passivates any outstanding
callbacks. No concerns of use-after-free.
* `ThenExactlyOnce` on the other hand guarantees to call the continuation
exactly once. If the client object is destroyed, the continuation receives a
cancellation error. You need to ensure any referenced objects are still alive
by the time the continuation runs, which may be an unspecified time after the
client object is destroyed. You should choose `ThenExactlyOnce` if your
continuation must be called exactly once, such as when interfacing with
`fpromise` completers or FIDL server completers, or during unit tests.
#### Recommendation
As a rule of thumb:
* If your callback looks like `client_->Foo([this]`, use `Then` (note that
`client_` is a member variable).
* If your callback looks like
* `client->Foo([completer]`, or
* `client->Foo([]`, or
* `client->Foo([&]` (common in unit tests),
* callback captures a weak pointer or a strong pointer,
* use `ThenExactlyOnce`.
Do not capture objects of differing lifetimes such that only a subset of the
objects are alive when the continuation runs.
### Zircon channel transport vs driver transport
A FIDL protocol is associated with a corresponding transport, specified in the
FIDL definition, which determines the kinds of resources that may flow through
the protocol, and may affect the generated API for sending and receiving
messages. The C++ bindings support two transports:
The Zircon channel transport is represented by endpoint types
`fidl::ClientEnd<SomeProtocol>` and `fidl::ServerEnd<SomeProtocol>`.
The driver transport uses endpoint types `fdf::ClientEnd<SomeProtocol>` and
`fdf::ServerEnd<SomeProtocol>`.
### Arenas
Arenas objects manage a pool of memory buffers and provide efficient allocation.
They are used pervasively in wire domain objects and wire clients and servers to
avoid expensive copies.
Arenas are not used with natural domain objects and associated clients and
servers, which encapsulate details about memory allocation.
You may use `fidl::Arena` to create wire domain objects which live on that
arena. See [memory management][memory-ownership].
When using protocols over the [driver transport][driver-transport] with wire
domain objects, `fdf::Arena` objects should be used to allocate the buffers
needed to encode messages.
<!-- xrefs -->
[domain-objects]: basics/domain-objects.md
[server]: basics/server.md
[async]: basics/client.md
[sync]: basics/sync-client.md
[pipelining]: topics/request-pipelining.md
[services]: topics/services.md
[async-completers]: topics/async-completer.md
[memory-ownership]: topics/wire-memory-ownership.md
[threading]: topics/threading.md
[driver-transport]: topics/driver-transport.md
[client]: topics/threading.md#client
[shared-client]: topics/threading.md#sharedclient