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

Quick reference

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

Taking the examples.keyvaluestore.baseline library as example:

{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/new/key_value_store/baseline/fidl/key_value_store.test.fidl" exclude_regexp="^//.*" %}

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.

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

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

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

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.

Note: prefer natural types unless optimizing for critical performance and memory allocation. Refer to the C++ tutorials.

Natural types

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

Wire types

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

Client and server APIs

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

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

High-level C++ bindings

<<../../../../_common/_hlcpp_notice.md>>

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

Refer to the HLCPP tutorial to get started.

Summary

CategoryNew C++ with wire typesNew C++ with natural typesHigh-level C++
audiencedrivers and performance-critical applicationshigh-level serviceshigh-level services
abstraction overheadRAII closing of handles [^1]heap allocation, construction, destructionheap allocation, construction, destruction
type safe typesenums, structs, unions, handles, protocolsenums, structs, unions, handles, protocolsenums, structs, unions, handles, protocols
storagestack, user-provided buffer, or heapheapheap
lifecyclemanual or automatic freeautomatic free (RAII)automatic free (RAII)
receive behaviordecode in-placedecode into heapdecode then move to heap
send behaviorcopy or vectorizecopycopy
calling protocol methodsfree functions or proxyfree functions or proxycall through proxies, register callbacks
implementing protocol methodsmanual dispatch or implement stub interfaceimplement stub interfaceimplement stub object, invoke callbacks
async clientyesyesyes
async serveryes (unbounded) [^2]yes (unbounded) [^2]yes (unbounded)
parallel server dispatchyes [^3]yes [^3]no
generated code footprintlargelargelarge

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

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

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