blob: bf456931a3306ca70156103f084df7d45c345ae3 [file] [log] [blame] [view]
G# Low-level C++ language FIDL tutorial
[TOC]
## About this tutorial
This tutorial describes how to make client calls and write servers in C++
using the Low-Level C++ Bindings (LLCPP).
[Getting Started](#getting-started) has a walk-through of using the bindings
with an example FIDL library. The [reference](#reference) section documents
the detailed bindings interface and design.
See [Comparing C, Low-Level C++, and High-Level C++ Language
Bindings][c-family-comparison] for a comparative analysis of the goals and
use cases for all the C-family language bindings.
Note: LLCPP is in currently in beta. The bindings are designed to exploit the
compatibility between FIDL wire-format and C++ memory layouts, and offer
precise control over allocation. As such, viewers are encouraged to
familiarize themselves with the [C Language Bindings](tutorial-c.md#reference)
and the [FIDL wire-format][wire-format]. Parts of this
tutorial assume knowledge of these related concepts.
# Getting started
Two build setups exist in the source tree: the Zircon build and the Fuchsia
build. The LLCPP code generator is not supported by the Zircon build. Therefore,
the steps to use the bindings depend on where the consumer code is located:
* **Code is outside `zircon/`:**
Add `//[library path]:[library name]_llcpp` to the GN dependencies e.g.
`"//sdk/fidl/fuchsia.math:fuchsia.math_llcpp"`, and the bindings code
will be automatically generated as part of the build.
* **Code is inside `zircon/`:**
Add a GN dependency of the form: `"$zx/system/fidl/[library-name]:llcpp"`,
e.g. `"$zx/system/fidl/fuchsia-mem:llcpp"`, and the bindings code will be
automatically generated as part of the build.
## Preliminary concepts
* **Decoded message:**
A FIDL message in [decoded form][wire-format-decoded]
is a contiguous buffer that is directly accessible by reinterpreting the
memory as the corresponding LLCPP FIDL type. That is, all pointers point
within the same buffer, and the pointed objects are in a specific order
defined by the FIDL wire-format. When making a call, a response buffer is
used to decode the response message.
* **Encoded message:**
A FIDL message in [encoded form][wire-format-encoded]
is an opaque contiguous buffer plus an array of handles. The buffer is
of the same length as the decoded counterpart, but pointers are replaced
with placeholders, and handles are moved to the accompanying array.
When making a call, a request buffer is used to encode the request message.
* **Message linearization:**
FIDL messages have to be in a contiguous buffer packed according to the
wire-format. When making a call however, the arguments to the bindings code
and out-of-line objects are usually scattered in memory, unless careful
attention is spent to follow the wire-format order. The process of walking
down the tree of objects and packing them is termed *linearization*, and
usually involves `O(message size)` copying.
* **Message layout:**
The in-memory layout of LLCPP structures is the same as the layout of the
wire format. The LLCPP objects can be thought of as a view over the
encoded message..
* **Message ownership:**
LLCPP objects use [`tracking_ptr`](#Pointers-and-memory-ownership) smart
pointers to manage ownership and track whether an object is heap allocated
and owned or user-managed and unowned.
## Generated API overview
Low-Level C++ bindings are full featured, and support control over allocation as
well as zero-copy encoding/decoding. (Note that contrary to the C bindings they
are meant to replace, the LLCPP bindings cover non-simple messages.)
Let's use this FIDL protocol as a motivating example:
```fidl
// fleet.fidl
library fuchsia.fleet;
struct Planet {
string name;
float64 mass;
handle<channel> radio;
};
```
The following code is generated (simplified for readability):
```cpp
// fleet.h
struct Planet {
fidl::StringView name;
double mass;
zx::channel radio;
};
```
Note that `string` maps to `fidl::StringView`, hence the `Planet` struct
will not own the memory associated with the `name` string. Rather, all strings
point within some buffer space that is managed by the bindings library, or that
the caller could customize. The same goes for the `fidl::VectorView<Planet>`
in the code below.
Continuing with the FIDL protocol:
```fidl
// fleet.fidl continued...
protocol SpaceShip {
SetHeading(int16 heading);
ScanForPlanets() -> (vector<Planet> planets);
DirectedScan(int16 heading) -> (vector<Planet> planets);
-> OnBeacon(int16 heading);
};
```
The following code is generated (simplified for readability):
```cpp
// fleet.h continued...
class SpaceShip final {
public:
struct SetHeadingRequest final {
fidl_message_header_t _hdr;
int16_t heading;
};
struct ScanForPlanetsResponse final {
fidl_message_header_t _hdr;
fidl::VectorView<Planet> planets;
};
using ScanForPlanetsRequest = fidl::AnyZeroArgMessage;
struct DirectedScanRequest final {
fidl_message_header_t _hdr;
int16_t heading;
};
struct DirectedScanResponse final {
fidl_message_header_t _hdr;
fidl::VectorView<Planet> planets;
};
class SyncClient final { /* ... */ };
class Call final { /* ... */ };
class Interface { /* ... */ };
static bool TryDispatch(Interface* impl, fidl_msg_t* msg, fidl::Transaction* txn);
static bool Dispatch(Interface* impl, fidl_msg_t* msg, fidl::Transaction* txn);
class ResultOf final { /* ... */ };
class UnownedResultOf final { /* ... */ };
class InPlace final { /* ... */ };
// Generated classes for thread-safe async-capable client.
struct AsyncEventHandlers {
std::variant<fit::callback<void(int16_t)>,
fit::callback<void(fidl::DecodedMessage<OnBeaconResponse>)>>
on_beacon;
};
class ScanForPlanetsResponseContext { /* ... */ };
class DirectedScanResponseContext { /* ... */ };
class ClientImpl { /* ... */ };
};
```
Notice that every request and response is modelled as a `struct`:
`SetHeadingRequest`, `ScanForPlanetsResponse`, etc.
In particular, `ScanForPlanets()` has a request that contains no arguments, and
we provide a special type for that, `fidl::AnyZeroArgMessage`.
Following those, there are three related concepts in the generated code:
+ [`SyncClient`](#sync-client): A class that owns a Zircon channel, providing
methods to make requests to the FIDL server.
+ [`Call`](#static-functions): A class that contains static functions to make
sync FIDL calls directly on an unowned channel, avoiding setting up a
`SyncClient`. This is similar to the simple client wrappers from the C
bindings, which take a `zx_handle_t`.
+ `Interface` and `[Try]Dispatch`: A server should implement the `Interface`
pure virtual class, which allows `Dispatch` to call one of the defined
handlers with a received FIDL message.
[`[Unowned]ResultOf`](#resultof-and-unownedresultof) are "scoping" classes
containing return type definitions of FIDL calls inside `SyncClient` and `Call`.
This allows one to conveniently write `ResultOf::SetHeading` to denote the
result of calling `SetHeading`.
[`InPlace`](#in_place-calls) is another "scoping" class that houses functions
to make a FIDL call with encoding and decoding performed in-place directly on
the user buffer. It is more efficient than those `SyncClient` or `Call`, but
comes with caveats. We will dive into these separately.
## Client API
### Sync client `(Protocol::SyncClient)`
The following code is generated for `SpaceShip::SyncClient`. Each FIDL method
always correspond to two overloads which differ in memory management strategies,
termed *flavors* in LLCPP: *managed flavor* and *caller-allocating flavor*.
```cpp
class SyncClient final {
public:
SyncClient(zx::channel channel);
// FIDL: SetHeading(int16 heading);
ResultOf::SetHeading SetHeading(int16_t heading);
UnownedResultOf::SetHeading SetHeading(fidl::BytePart request_buffer, int16_t heading);
// FIDL: ScanForPlanets() -> (vector<Planet> planets);
ResultOf::ScanForPlanets ScanForPlanets();
UnownedResultOf::ScanForPlanets ScanForPlanets(fidl::BytePart response_buffer);
// FIDL: DirectedScan(int16 heading) -> (vector<Planet> planets);
ResultOf::DirectedScan DirectedScan(int16_t heading);
UnownedResultOf::DirectedScan DirectedScan(fidl::BytePart request_buffer, int16_t heading,
fidl::BytePart response_buffer);
};
```
The one-way FIDL method `SetHeading(int16 heading)` maps to:
+ `ResultOf::SetHeading SetHeading(int16_t heading)`:
This is the *managed flavor*.
Buffer allocation for requests and responses are entirely handled within this
function, as is the case in simple C bindings. 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. Here is an example of using it:
```cpp
// Create a client from a Zircon channel.
SpaceShip::SyncClient client(zx::channel(client_end));
// Calling |SetHeading| with heading = 42.
SpaceShip::ResultOf::SetHeading result = client.SetHeading(42);
// Check the transport status (encoding error, channel writing error, etc.)
if (result.status() != ZX_OK) {
// Handle error...
}
```
In general, the managed flavor is easier to use, but may result in extra
allocation. See [ResultOf](#resultof-and-unownedresultof) for details on buffer
management.
+ `UnownedResultOf::SetHeading SetHeading(fidl::BytePart request_buffer, int16_t heading)`:
This is the *caller-allocating flavor*, which defers all memory allocation
responsibilities to the caller.
Here we see an additional parameter `request_buffer` which is always the first
argument in this flavor. The type `fidl::BytePart` 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. If `SetHeading` had a return value, this flavor would ask for
a `response_buffer` too, as the last argument. Here is an example of using it:
```cpp
// Call SetHeading with an explicit buffer, there are multiple ways...
// 1. On the stack
fidl::Buffer<SetHeadingRequest> request_buffer;
auto result = client.SetHeading(request_buffer.view(), 42);
// 2. On the heap
auto request_buffer = std::make_unique<fidl::Buffer<SetHeadingRequest>>();
auto result = client.SetHeading(request_buffer->view(), 42);
// 3. Some other means, e.g. thread-local storage
constexpr uint32_t request_size = fidl::MaxSizeInChannel<SetHeadingRequest>();
uint8_t* buffer = allocate_buffer_of_size(request_size);
fidl::BytePart request_buffer(/* data = */buffer, /* capacity = */request_size);
auto result = client.SetHeading(std::move(request_buffer), 42);
// 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 `UnownedResultOf`).
> Make sure the buffers outlive the `result` object.
> See [UnownedResultOf](#resultof-and-unownedresultof).
Caution: Buffers passed to the bindings must be aligned to 8 bytes. The
`fidl::Buffer` helper class does this automatically. Failure to align would
result in a run-time error.
* * * *
The two-way FIDL method
`ScanForPlanets() -> (vector<Planet> planets)` maps to:
+ `ResultOf::ScanForPlanets ScanForPlanets()`:
This is the *managed flavor*. Different from the C bindings, response arguments
are not returned via out-parameters. Instead, they are accessed through the
return value. Here is an example to illustrate:
```cpp
// It is cleaner to omit the |UnownedResultOf::ScanForPlanets| result type.
auto result = client.ScanForPlanets();
// Check the transport status (encoding error, channel writing error, etc.)
if (result.status() != ZX_OK) {
// handle error & early exit...
}
// Obtains a pointer to the response struct inside |result|.
// This requires that the transport status is |ZX_OK|.
SpaceShip::ScanForPlanetsResponse* response = result.Unwrap();
// Access the |planets| response vector in the FIDL call.
for (const auto& planet : response->planets) {
// Do something with |planet|...
}
```
> When the managed flavor is used, the returned object (`result` in this
> example) manages ownership of all buffer and handles, while `result.Unwrap()`
> returns a view over it. Therefore, the `result` object must outlive any
> references to the response.
+ `UnownedResultOf::ScanForPlanets ScanForPlanets(fidl::BytePart response_buffer)`:
The *caller-allocating flavor* receives the message into `response_buffer`.
Here is an example using it:
```cpp
fidl::Buffer<ScanForPlanetsResponse> response_buffer;
auto result = client.ScanForPlanets(response_buffer.view());
if (result.status() != ZX_OK) { /* ... */ }
auto response = result.Unwrap();
// |response->planets| points to a location within |response_buffer|.
```
> The buffers passed to caller-allocating flavor do not have to be initialized.
> A buffer may be re-used multiple times, as long as it is large enough for
> the calls involved.
Note: Since each `Planet` has a handle `zx::channel radio`, and the
`fidl::VectorView<Planet>` type does not own the individual `Planet` objects,
there needs to be a reliable way to capture the lifetime of those handles.
Here the return value `result` owns them, and takes care of closing them when
it goes out of scope.
If any handle is `std::move`ed away, `result` would not accidentally close it.
### Async-capable Client (`fidl::Client<Protocol>`)
This client is thread-safe and supports both synchronous and asynchronous calls
as well as asynchronous event handling. It also supports use with a
multi-threaded dispatcher.
#### Creation
A client is created with a client-end `zx::channel`, an `async_dispatcher_t*`,
an optional hook (`OnClientUnboundFn`) to be invoked when the channel is
unbound, and an optional `AsyncEventHandlers` containing hooks to
be invoked on FIDL events.
```cpp
Client<SpaceShip> client;
zx_status_t status = client.Bind(
std::move(client_end), dispatcher,
// OnClientUnboundFn
[&](fidl::UnboundReason, zx_status_t, zx::channel) { /* ... */ },
// AsyncEventHandlers
{ .on_beacon = [&](int16_t) { /* ... */ } });
```
#### Unbinding
The channel may be unbound automatically in case of the server-end being closed
or due to an invalid message being received from the server. You may also
actively unbind the channel through `client.Unbind()`.
Unbinding is thread-safe. In any of these cases, ongoing and future operations
will not cause a fatal failure, only returning `ZX_ERR_CANCELED` where
appropriate.
If you provided an unbound hook, it is executed as task on the dispatcher,
providing a reason and error status for the unbinding. You may also recover
ownership of the client end of the channel through the hook. The unbound hook is
guaranteed to be run.
#### Interaction with dispatcher
All asynchronous responses, event handling, and error handling are done through
the `async_dispatcher_t*` provided on creation of a client. With the exception
of the dispatcher being shutdown, you can expect that all hooks provided to the
client APIs will be executed on a dispatcher thread (and not nested within other
user code).
NOTE: If you shutdown the dispatcher while there are any active bindings, the
unbound hook MAY be executed on the thread executing shutdown. As such, you MUST
not take any locks which could be taken by hooks provided to `fidl::Client` APIs
while executing `async::Loop::Shutdown()/async_loop_shutdown()`. (You should
probably ensure that no locks are held around shutdown anyway since it joins all
dispatcher threads, which may take locks in user code).
#### Outgoing FIDL methods
You can invoke outgoing FIDL APIs through the `fidl::Client<SpaceShip>`
instance, e.g. `client->SetHeading(0)`. The full generated API is given below:
```cpp
class ClientImpl final {
public:
fidl::StatusAndError SetHeading(int16_t heading);
fidl::StatusAndError SetHeading(fidl::BytePart _request_buffer,
int16_t heading);
fidl::StatusAndError ScanForPlanets(
fit::callback<void(fidl::VectorView<Planet> planets)> _cb);
fidl::StatusAndError ScanForPlanets(ScanForPlanetsResponseContext* _context);
ResultOf::ScanForPlanets ScanForPlanets_Sync(int16_t heading);
UnownedResultOf::ScanForPlanets ScanForPlanets_Sync(
fidl::BytePart _response_buffer, int16_t heading);
fidl::StatusAndError DirectedScan(fit::callback<void(fidl::VectorView<Planet> planets)> _cb);
fidl::StatusAndError DirectedScan(DirectedScanResponseContext* _context);
ResultOf::DirectedScan DirectedScan_Sync(int16_t heading);
UnownedResultOf::DirectedScan DirectedScan_Sync(
fidl::BytePart _request_buffer, int16_t heading,
fidl::BytePart _response_buffer);
};
```
Note that the one-way and synchronous two-way FIDL methods have a similar API to
the `SyncClient` versions. Aside from one-way methods directly returning
`fidl::StatusAndError` and the added `_Sync` on the synchronous methods, the
behavior is identical.
##### Asynchronous APIs
The *managed* flavor of the asynchronous two-way APIs simply takes a
`fit::callback` hook which is executed on response in a dispatcher thread. The
returned `fidl::StatusAndError` refers just to the status of the outgoing call.
```cpp
auto status = client->DirectedScan(0, [&]{ /* ... */ });
```
The *caller-allocated* flavor enables you to provide the storage for the
callback as well as any associated state. This is done through the generated
virtual `ResponseContext` classes:
```cpp
class DirectedScanResponseContext : public fidl::internal::ResponseContext {
public:
virtual void OnReply(fidl::DecodedMessage<DirectedScanResponse> msg) = 0;
};
```
You can derive from this class, implementing `OnReply()` and `OnError()`
(inherited from `fidl::internal::ResponseContext`). You can then allocate an
object of this type as required, passing a pointer to it to the API. The object
must stay alive until either `OnReply()` or `OnError()` is invoked by the
`Client`.
NOTE: If the `Client` is destroyed with outstanding asynchronous transactions,
`OnError()` will be invoked for all of the associated `ResponseContext`s.
### Static functions `(Protocol::Call)`
The following code is generated for `SpaceShip::Call`:
```cpp
class Call final {
public:
static ResultOf::SetHeading
SetHeading(zx::unowned_channel client_end, int16_t heading);
static UnownedResultOf::SetHeading
SetHeading(zx::unowned_channel client_end, fidl::BytePart request_buffer, int16_t heading);
static ResultOf::ScanForPlanets
ScanForPlanets(zx::unowned_channel client_end);
static UnownedResultOf::ScanForPlanets
ScanForPlanets(zx::unowned_channel client_end, fidl::BytePart response_buffer);
static ResultOf::DirectedScan
DirectedScan(zx::unowned_channel client_end, int16_t heading);
static UnownedResultOf::DirectedScan
DirectedScan(zx::unowned_channel client_end, fidl::BytePart request_buffer, int16_t heading,
fidl::BytePart response_buffer);
};
```
These methods are similar to those found in `SyncClient`. However, they do not
own the channel. This is useful if one is migrating existing code from the
C bindings to low-level C++. Another use case is when implementing C APIs
which take a raw `zx_handle_t`. For example:
```cpp
// C interface which does not own the channel.
zx_status_t spaceship_set_heading(zx_handle_t spaceship, int16_t heading) {
auto result = fuchsia::fleet::SpaceShip::Call::SetHeading(
zx::unowned_channel(spaceship), heading);
return result.status();
}
```
### ResultOf and UnownedResultOf
For a method named `Foo`, `ResultOf::Foo` is the return type of the *managed
flavor*. `UnownedResultOf::Foo` is the return type of the *caller-allocating
flavor*. Both 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.
* `const char* error() const` contains a brief error message when status is
not `ZX_OK`. Otherwise, returns `nullptr`.
* **(only for two-way calls)** `FooResponse* Unwrap()` returns a pointer
to the FIDL response message. For `ResultOf::Foo`, the pointer points to
memory owned by the result object. For `UnownedResultOf::Foo`, the pointer
points to a caller-provided buffer. `Unwrap()` should only be called when
the status is `ZX_OK`.
#### Allocation strategy And move semantics
`ResultOf::Foo` 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,
`ResultOf::Foo` instead contains a `std::unique_ptr` to a heap-allocated buffer.
Therefore, a `std::move()` on `ResultOf::Foo` may be costly if the response
buffer is inline: the content has to be copied, and pointers to out-of-line
objects have to be updated to locations within the destination object.
Consider the following snippet:
```cpp
int CountPlanets(ResultOf::ScanForPlanets result) { /* ... */ }
auto result = client.ScanForPlanets();
SpaceShip::ScanForPlanetsResponse* response = result.Unwrap();
Planet* planet = &response->planets[0];
int count = CountPlanets(std::move(result)); // Costly
// In addition, |response| and |planet| are invalidated due to the move
```
It may be written more efficiently as:
```cpp
int CountPlanets(fidl::VectorView<SpaceShip::Planet> planets) { /* ... */ }
auto result = client.ScanForPlanets();
int count = CountPlanets(result.Unwrap()->planets);
```
> 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.
### In-place calls {#inplace}
Both the *managed flavor* and the *caller-allocating flavor* will copy the
arguments into the request buffer. When there is out-of-line data involved,
*message linearization* is additionally required to collate them as per the
wire-format. When the request is large, these copying overhead can add up.
LLCPP supports making a call directly on a caller-provided buffer containing
a request message in decoded form, without any parameter copying. The request
is encoded in-place, hence the name of the scoping class `InPlace`.
```cpp
class InPlace final {
public:
static ::fidl::internal::StatusAndError
SetHeading(zx::unowned_channel client_end,
fidl::DecodedMessage<SetHeadingRequest> params);
static ::fidl::DecodeResult<ScanForPlanets>
ScanForPlanets(zx::unowned_channel client_end,
fidl::DecodedMessage<ScanForPlanetsRequest> params,
fidl::BytePart response_buffer);
static ::fidl::DecodeResult<DirectedScan>
DirectedScan(zx::unowned_channel client_end,
fidl::DecodedMessage<DirectedScanRequest> params,
fidl::BytePart response_buffer);
};
```
These functions always take a
[`fidl::DecodedMessage<FooRequest>`](#fidl_decodedmessage_t) which wraps the
user-provided buffer. To use it properly, initialize the request buffer with a
FIDL message in decoded form. *In particular, out-of-line objects have to be
packed according to the wire-format, and therefore any pointers in the message
have to point within the same buffer.*
When there is a response defined, the generated functions additionally ask for a
`response_buffer` as the last argument. The response buffer does not have to be
initialized.
```cpp
// Allocate buffer for in-place call
fidl::Buffer<SetHeadingRequest> request_buffer;
fidl::BytePart request_bytes = request_buffer.view();
memset(request_bytes.data(), 0, request_bytes.capacity());
// Manually construct the message
auto msg = reinterpret_cast<SetHeadingRequest*>(request_bytes.data());
msg->heading = 42;
// Here since our message is a simple struct,
// the request size is equal to the capacity.
request_bytes.set_actual(request_bytes.capacity());
// Wrap with a fidl::DecodedMessage
fidl::DecodedMessage<SetHeadingRequest> request(std::move(request_bytes));
// Finally, make the call.
auto result = SpaceShip::InPlace::SetHeading(channel, std::move(request));
// Check result.status(), result.error()
```
Despite the verbosity, there is actually very little work involved.
The buffer passed to the underlying `zx_channel_call` system call is in fact
`request_bytes`. The performance benefits become apparent when say the request
message contains a large inline array. One could set up the buffers once, then
make repeated calls while mutating the array by directly editing the buffer
in between.
Key Point: in-place calls only reduce overhead in the request part of the call.
Responses are already processed in-place even in the managed and
caller-allocating flavors.
## Server API
```cpp
class Interface {
public:
virtual void SetHeading(int16_t heading,
SetHeadingCompleter::Sync completer) = 0;
class ScanForPlanetsCompleterBase {
public:
void Reply(fidl::VectorView<Planet> planets);
void Reply(fidl::BytePart buffer, fidl::VectorView<Planet> planets);
void Reply(fidl::DecodedMessage<ScanForPlanetsResponse> params);
};
using ScanForPlanetsCompleter = fidl::Completer<ScanForPlanetsCompleterBase>;
virtual void ScanForPlanets(ScanForPlanetsCompleter::Sync completer) = 0;
class DirectedScanCompleterBase {
public:
void Reply(fidl::VectorView<Planet> planets);
void Reply(fidl::BytePart buffer, fidl::VectorView<Planet> planets);
void Reply(fidl::DecodedMessage<DirectedScanResponse> params);
};
using DirectedScanCompleter = fidl::Completer<DirectedScanCompleterBase>;
virtual void DirectedScan(int16_t heading, DirectedScanCompleter::Sync completer) = 0;
};
bool TryDispatch(Interface* impl, fidl_msg_t* msg, fidl::Transaction* txn);
```
The generated `Interface` class has pure virtual functions corresponding to the
method calls defined in the FIDL protocol. One may override these functions in
a subclass, and dispatch FIDL messages to a server instance by calling
`TryDispatch`.
The bindings runtime would invoke these handler functions appropriately.
```cpp
class MyServer final : fuchsia::fleet::SpaceShip::Interface {
public:
void SetHeading(int16_t heading,
SetHeadingCompleter::Sync completer) override {
// Update the heading...
}
void ScanForPlanets(ScanForPlanetsCompleter::Sync completer) override {
fidl::VectorView<Planet> discovered_planets = /* perform planet scan */;
// Send the |discovered_planets| vector as the response.
completer.Reply(std::move(discovered_planets));
}
void DirectedScan(int16_t heading, DirectedScanCompleter::Sync completer) override {
fidl::VectorView<Planet> discovered_planets = /* perform a directed planet scan */;
// Send the |discovered_planets| vector as the response.
completer.Reply(std::move(discovered_planets));
}
};
```
Each handler function has an additional last argument `completer`.
It captures the various ways one may complete a FIDL transaction, by sending a
reply, closing the channel with epitaph, etc.
For FIDL methods with a reply e.g. `ScanForPlanets`, the corresponding completer
defines up to three overloads of a `Reply()` function
(managed, caller-allocating, in-place), similar to the client side API.
The completer always defines a `Close(zx_status_t)` function, to close the
connection with a specified epitaph.
NOTE: Each `Completer` object must only be accessed by one thread at a time.
Simultaneous access from multiple threads will result in a crash.
### Responding asynchronously {#async-server}
Notice that the type for the completer `ScanForPlanetsCompleter::Sync` has
`::Sync`. This indicates the default mode of operation: the server must
synchronously make a reply before returning from the handler function.
Enforcing this allows optimizations: the bookkeeping metadata for making
a reply may be stack-allocated.
To asynchronously make a reply, one may call the `ToAsync()` method on a `Sync`
completer, converting it to `ScanForPlanetsCompleter::Async`. The `Async`
completer supports the same `Reply()` functions, and may out-live the scope of
the handler function by e.g. moving it into a lambda capture.
```cpp
void ScanForPlanets(ScanForPlanetsCompleter::Sync completer) override {
// Suppose scanning for planets takes a long time,
// and returns the result via a callback...
EnqueuePlanetScan(some_parameters)
.OnDone([completer = completer.ToAsync()] (auto planets) mutable {
// Here the type of |completer| is |ScanForPlanetsCompleter::Async|.
completer.Reply(std::move(planets));
});
}
```
### Parallel message handling
NOTE: This use-case is currently possible only using the
[lib/fidl](/zircon/system/ulib/fidl) bindings.
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.
```cpp
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));
}
```
# Reference
## Design
### Goals
* Support encoding and decoding FIDL messages with C++17.
* Provide fine-grained control over memory allocation.
* More type-safety and more features than the C language bindings.
* Match the size and efficiency of the C language bindings.
* Depend only on a small subset of the standard library.
* Minimize code bloat through table-driven encoding and decoding.
* Reuse encoders, decoders, and coding tables generated for C language
bindings.
## Pointers and memory ownership {#memory-ownership}
LLCPP objects use special smart pointers called `tracking_ptr` to keep track of memory ownership.
With `tracking_ptr`, LLCPP makes it possible for your code to easily set a value and forget
about ownership: `tracking_ptr` will take care of freeing memory when it goes out of scope.
These pointers have two states:
* unowned (constructed from an `unowned_ptr_t`)
* heap allocated and owned (constructed from a `std::unique_ptr`)
When the contents is owned, a `tracking_ptr` behaves like a `unique_ptr` and the pointer is
deleted on destruction. In the unowned state, `tracking_ptr` behaves like a raw pointer and
destruction is a no-op.
`tracking_ptr` is move-only and has an API closely matching `unique_ptr`.
### Types of object allocation
`tracking_ptr` makes it possible to create LLCPP objects with several allocation strategies.
The allocation strategies can be mixed and matched within the same code.
#### Heap allocation
To heap allocate objects, use the standard `std::make_unique`.
An example with an optional uint32 field represented as a `tracking_ptr`.
```
MyStruct s;
s.opt_uint32_field = std::make_unique<uint32_t>(123);
```
This applies to all union and table fields and data arrays within vectors and strings.
Vector and string data arrays must use the array specialization of `std::unique_ptr`,
which takes the element count as an argument.
```
VectorView<uint32_t> vec;
vec.set_data(std::make_unique<uint32_t[]>(10));
```
To copy a collection to a `VectorView`, use `heap_copy_vec`.
```
std::vector<uint32_t> vec;
fidl::VectorView<uint32_t> vv = heap_copy_vec(vec);
```
To copy a string to a `StringView`, use `heap_copy_str`.
```
std::string_view str = "hello world";
fidl::StringView sv = heap_copy_str(str);
```
#### Allocators
FIDL provides an `Allocator` API that enables creating `tracking_ptr`s to LLCPP objects through a
number of allocation algorithms. Currently, `BufferThenHeapAllocator`, `UnsafeBufferAllocator`, and
`HeapAllocator` are available in fidl namespace.
The `BufferThenHeapAllocator` allocates from an in-band fixed-size buffer (can be used for stack
allocation), but falls back to heap allocation if the in-band buffer has been exhausted (to avoid
unnecessary unfortunate surprises). Be aware that excessive stack usage can cause its own problems,
so consider using a buffer size that comfortably fits on the stack, or consider putting the whole
BufferThenHeapAllocator on the heap if the buffer needs to be larger than fits on the stack, or
consider using HeapAllocator. Allocations must be assumed to be gone upon destruction of the
`BufferThenHeapAllocator` used to make them.
The `HeapAllocator` always allocates from the heap, and is unique among allocators (so far) in that
any/all of the `HeapAllocator` allocations can out-live the `HeapAllocator` instance used to make
them.
The `UnsafeBufferAllocator` is unsafe in the sense that it lacks heap failover, so risks creating
unfortunate data-dependent surprises unless the buffer size is absolutely guaranteed to be large
enough including the internal destructor-tracking overhead. If the internal buffer is exhausted,
make<>() will panic the entire process. Consider using `BufferThenHeapAllocator` instead. Do not
use `UnsafeBufferAllocator` without rigorously testing that the worst-case set of cumulative
allocations made via the allocator all fit without a panic, and consider how the rigor will be
maintained as code and FIDL tables are changed.
Example:
```
BufferThenHeapAllocator<2048> allocator;
MyStruct s;
s.opt_uint32_field = allocator.make<uint32_t>(123);
```
The arguments to `allocator.make` are identical to the arguments to `std::make_unique`.
This also applies to VectorViews.
```
BufferThenHeapAllocator<2048> allocator;
fidl::VectorView<uint32_t> vec;
vec.set_data(allocator.make<uint32_t[]>(10));
```
To copy a collection to a `VectorView` using an allocator, use `copy_vec`.
```
BufferThenHeapAllocator<2048> allocator;
std::vector<uint32_t> vec;
fidl::VectorView<uint32_t> vv = fidl::copy_vec(allocator, vec);
```
To create a copy of a string using an allocator, use `copy_str`.
```
BufferThenHeapAllocator<2048> allocator;
std::string_view str = "hello world";
fidl::StringView sv = fidl::copy_str(allocator, str);
```
#### Unowned pointers
In addition to the managed allocation strategies, it is also possible to directly
create pointers to memory unowned by FIDL. This is discouraged, as it is easy to
accidentally create use-after-free bugs. `unowned_ptr` exists to explicitly mark
pointers to FIDL-unowned memory.
The `unowned_ptr` helper is the recommended way to create `unowned_ptr_t`s,
which is more ergonomic than using the `unowned_ptr_t` constructor directly.
```
MyStruct s;
uint32_t i = 123;
s.opt_uint32_field = fidl::unowned_ptr(&i);
```
To create a `VectorView` from a collection using an unowned pointer to the
collection's data array, use `unowned_vec`.
```
std::vector<uint32_t> vec;
fidl::VectorView<uint32_t> vv = fidl::unowned_vec(vec);
```
To create a `StringView` from unowned memory, use `unowned_str`.
```
const char arr[] = {'h', 'e', 'l', 'l', 'o'};
fidl::StringView sv = fidl::unowned_str(arr, 5);
```
A `StringView` can also be created directly from string literals without using
`unowned_ptr`.
```
fidl::StringView sv = "hello world";
```
## Code generator
### Mapping FIDL types to low-level C++ types
This is the mapping from FIDL types to Low-Level C++ types which the code
generator produces.
FIDL | Low-Level C++
--------------------------------------------|------------------------------------------------------
`bool` | `bool`, *(requires sizeof(bool) == 1)*
`int8` | `int8_t`
`uint8` | `uint8_t`
`int16` | `int16_t`
`uint16` | `uint16_t`
`int32` | `int32_t`
`uint32` | `uint32_t`
`int64` | `int64_t`
`uint64` | `uint64_t`
`float32` | `float`
`float64` | `double`
`handle`, `handle?` | `zx::handle`
`handle<T>`,`handle<T>?` | `zx::T` *(subclass of zx::object\<T\>)*
`string` | `fidl::StringView`
`string?` | `fidl::StringView`
`vector<T>` | `fidl::VectorView<T>`
`vector<T>?` | `fidl::VectorView<T>`
`array<T>:N` | `fidl::Array<T, N>`
*protocol, protocol?* | `zx::channel`
*request\<Protocol\>, request\<Protocol\>?* | `zx::channel`
*struct* Struct | *struct* Struct
*struct?* Struct | *struct* Struct*
*table* Table | *struct* Table
*union* Union | *struct* Union
*union?* Union | *struct* Union*
*union* Union | *struct* Union
*union?* Union | *struct* Union*
*enum* Foo | *enum class Foo : data type*
#### fidl::StringView
Defined in [lib/fidl/llcpp/string_view.h](/zircon/system/ulib/fidl/include/lib/fidl/llcpp/string_view.h)
Holds a reference to a variable-length string stored within the buffer. C++
wrapper of **fidl_string**. Does not own the memory of the contents.
`fidl::StringView` may be constructed by supplying the pointer and number of
UTF-8 bytes (excluding trailing `\0`) separately. Alternatively, one could pass
a C++ string literal, or any value which implements `[const] char* data()`
and `size()`. The string view would borrow the contents of the container.
It is memory layout compatible with **fidl_string**.
#### fidl::VectorView\<T\>
Defined in [lib/fidl/llcpp/vector_view.h](/zircon/system/ulib/fidl/include/lib/fidl/llcpp/vector_view.h)
Holds a reference to a variable-length vector of elements stored within the
buffer. C++ wrapper of **fidl_vector**. Does not own the memory of elements.
`fidl::VectorView` may be constructed by supplying the pointer and number of
elements separately. Alternatively, one could pass any value which supports
[`std::data`](https://en.cppreference.com/w/cpp/iterator/data), such as a
standard container, or an array. The vector view would borrow the contents of
the container.
It is memory layout compatible with **fidl_vector**.
#### fidl::Array\<T, N\>
Defined in [lib/fidl/llcpp/array.h](/zircon/system/ulib/fidl/include/lib/fidl/llcpp/array.h)
Owns a fixed-length array of elements.
Similar to `std::array<T, N>` but intended purely for in-place use.
It is memory layout compatible with FIDL arrays, and is standard-layout.
The destructor closes handles if applicable e.g. it is an array of handles.
#### Tables
The following example table will be used in this section:
```
table MyTable {
1: uint32 x;
2: uint32 y;
};
```
Tables can be built using the associated table builder. For `MyTable`, the associated builder
would be `MyTable::Builder` which can be used as follows:
```
MyTable table = MyTable::Builder(std::make_unique<MyTable::Frame>())
.set_x(std::make_unique<uint32_t>(10))
.set_y(std::make_unique<uint32_t(20))
.build();
```
`MyTable::Frame` is the table's `Frame` - essentially its internal storage. The internal storage
needs to be allocated separately from the builder because LLCPP maintains the object layout of
the underlying wire format.
In addition to assigning fields with `std::unique_ptr`, any of the allocation strategies previously
metioned can be alternatively used.
## Bindings library
### Dependencies
The low-level C++ bindings depend only on a small subset of header-only parts
of the standard library. As such, they may be used in environments where linking
against the C++ standard library is discouraged or impossible.
### Helper types
#### fidl::DecodedMessage\<T\>
Defined in [lib/fidl/llcpp/decoded_message.h](/zircon/system/ulib/fidl/include/lib/fidl/llcpp/decoded_message.h)
Manages a FIDL message in [decoded form][wire-format-dual-forms].
The message type is specified in the template parameter `T`.
This class takes care of releasing all handles which were not consumed
(std::moved from the decoded message) when it goes out of scope.
`fidl::Encode(std::move(decoded_message))` encodes in-place.
#### fidl::EncodedMessage\<T\>
Defined in [lib/fidl/llcpp/encoded_message.h](/zircon/system/ulib/fidl/include/lib/fidl/llcpp/encoded_message.h)
Holds a FIDL message in [encoded form][wire-format-dual-forms],
that is, a byte array plus a handle table.
The bytes part points to an external caller-managed buffer, while the handles part
is owned by this class. Any handles will be closed upon destruction.
`fidl::Decode(std::move(encoded_message))` decodes in-place.
##### Example
```cpp
zx_status_t SayHello(const zx::channel& channel, fidl::StringView text,
zx::handle token) {
assert(text.size() <= MAX_TEXT_SIZE);
// Manually allocate the buffer used for this FIDL message,
// here we assume the message size will not exceed 512 bytes.
uint8_t buffer[512] = {};
fidl::DecodedMessage<example::Animal::SayRequest> decoded(
fidl::BytePart(buffer, 512));
// Fill in header and contents
example::Animal::SetTransactionHeaderFor::SayRequest(&decoded);
decoded.message()->text = text;
// Handle types have to be moved
decoded.message()->token = std::move(token);
// Encode the message in-place
fidl::EncodeResult<example::Animal::SayRequest> encode_result =
fidl::Encode(std::move(decoded));
if (encode_result.status != ZX_OK) {
return encode_result.status;
}
fidl::EncodedMessage<example::Animal::SayRequest>& encoded =
encode_result.message;
return channel.write(0, encoded.bytes().data(), encoded.bytes().size(),
encoded.handles().data(), encoded.handles().size());
}
```
<!-- xrefs -->
[c-family-comparison]: /docs/development/languages/fidl/guides/c-family-comparison.md
[wire-format]: /docs/reference/fidl/language/wire-format
[wire-format-decoded]: /docs/reference/fidl/language/wire-format/README.md#Decoded-Messages
[wire-format-encoded]: /docs/reference/fidl/language/wire-format/README.md#Encoded-Messages
[wire-format-dual-forms]: /docs/reference/fidl/language/wire-format/README.md#Encoded-Messages#Dual-Forms_Encoded-vs-Decoded