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
, andstd::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
, andstd::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 afidl::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 |
-
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. ↩ -
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. ↩ -
The bindings library lib/fidl enables parallel dispatch using the
EnableNextDispatch()
API defined in lib/fidl/cpp/wire/async_transaction.h. ↩