| # 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.md) 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](../reference/wire-format/README.md). 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](../reference/wire-format#Decoded-Messages) |
| 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](../reference/wire-format#Encoded-Messages) |
| 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 Ownership:** |
| Crucially, LLCPP generated structures are views over some underlying buffer; |
| they do not own memory or handles located out-of-line. In practice, one |
| must ensure the object managing the buffer outlives the views. |
| |
| ## 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); |
| }; |
| ``` |
| |
| 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 { /* ... */ }; |
| }; |
| ``` |
| |
| 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 `SpaceShape::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. |
| |
| ### Static Functions `(Protocol::Call)` |
| |
| The following code is generated for `SpaceShape::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 |
| |
| 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 = SpaceShape::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(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(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. |
| |
| ### Responding Asynchronously |
| |
| 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(planets); |
| }); |
| } |
| ``` |
| |
| ### Parallel Message Handling |
| |
| NOTE: This use-case is currently possible only using the |
| [lib/fidl-async](/zircon/system/ulib/fidl-async) 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(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. |
| |
| ## 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 | (not yet supported) |
| *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. |
| |
| ## 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](../reference/wire-format#Dual-Forms_Encoded-vs-Decoded). |
| 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](../reference/wire-format#Dual-Forms_Encoded-vs-Decoded), |
| 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()); |
| } |
| ``` |