blob: f1e6052cd34e8c50e29faa3942961a9be24f43a1 [file] [log] [blame] [view] [edit]
# FIDL 2.0: C++ Language Bindings
Status: DRAFT
Author: jeffbrown@google.com
This document is a description of the Fuchsia Interface Definition Language v2.0
(FIDL) implementation for C++, including its libraries and code generator. Once
ratified, it will be transformed into a Markdown file stored in the source tree.
See [FIDL 2.0: Overview](index.md) for more information about FIDL's overall
purpose, goals, and requirements, as well as links to related documents.
This specification builds on the [FIDL 2.0: C Language
Bindings](c-language-bindings.md) and reuses many of its elements where
appropriate.
**WORK IN PROGRESS**
[TOC]
## Design
### Goals
* Support encoding and decoding FIDL messages with C++14.
* Small, fast, efficient.
* Depend only on a small subset of the standard library.
* Minimize code expansion through table-driven encoding and decoding.
* Support two usage styles using different generated code: native and
idiomatic.
* All code produced to support the idiomatic style can be stripped out if
unused.
* Reuse encoders, decoders, and data tables generated for C language bindings.
### Native Usage Style
* Optimized to meet the needs of low-level systems programming while providing
slightly more safety and convenience than the C bindings.
* Represent data structures whose memory layout coincides with the wire
format.
* Support in-place access and construction of FIDL messages.
* Defer all memory allocation decisions to the client.
* Code generator only produces type declarations, data tables, and simple
inline functions.
* Client is fully responsible for dispatching incoming method calls on
interfaces (write their own switch statement and invoke argument decode
functions).
* Essentially just like C language bindings but using zero-cost C++ features
such as namespaces, string views, and array containers.
* Specifically does not use owned handle types such as zx::handle (libzx)
because they require making non-POD structs with constructors and
destructors.
* This is worth discussing further. Using zx::handle would be convenient
but it leads to awkward questions such as to whether destruction of FIDL
structs should be recursive (follow pointers, iterate over array
members). These are more easily tackled in the idiomatic form, hence it
is tempting to let the native form be purely POD and avoid destructors.
* Could consider making an zx::unowned_handle which lacks a destructor and
would therefore be POD.
### Idiomatic Usage Style
* 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 copy (move) data from in-place buffers to idiomatic heap allocated
objects.
* Can copy (move) data from idiomatic heap allocated objects to in-place
buffers.
* Code generator produces somewhat more code, including constructors,
destructors, interface proxies, interface stubs, copy/move functions, and
conversions to/from native style.
* Client performs interface dispatch by subclassing a provided stub and
implementing the virtual methods for each operation.
### Comparison of Usage Styles
<table>
<tr>
<td>
</td>
<td><strong>C style</strong>
</td>
<td><strong>C++ native style</strong>
</td>
<td><strong>C++ idiomatic style</strong>
</td>
</tr>
<tr>
<td><strong>audience</strong>
</td>
<td>drivers
</td>
<td>drivers
</td>
<td>high-level services
</td>
</tr>
<tr>
<td><strong>abstraction overhead</strong>
</td>
<td>almost zero
</td>
<td>almost zero
</td>
<td>heap allocation, construction, destruction
</td>
</tr>
<tr>
<td><strong>type safe types</strong>
</td>
<td>enums, structs, unions
</td>
<td>enums, structs, unions
</td>
<td>enums, structs, unions, handles, interfaces
</td>
</tr>
<tr>
<td><strong>storage</strong>
</td>
<td>in-place buffer
</td>
<td>in-place buffer
</td>
<td>heap
</td>
</tr>
<tr>
<td><strong>lifecycle</strong>
</td>
<td>manual free (POD)
</td>
<td>manual free (POD)
</td>
<td>automatic free (RAII)
</td>
</tr>
<tr>
<td><strong>receive behavior</strong>
</td>
<td>decode in-place
</td>
<td>decode in-place
</td>
<td>decode then move to heap
</td>
</tr>
<tr>
<td><strong>send behavior</strong>
</td>
<td>encode in-place
</td>
<td>encode in-place
</td>
<td>move to buffer then encode
</td>
</tr>
<tr>
<td><strong>calling interface methods</strong>
</td>
<td>manual proxy
</td>
<td>manual proxy
</td>
<td>call through proxies, register callbacks
</td>
</tr>
<tr>
<td><strong>implementing interface methods</strong>
</td>
<td>manual dispatch
</td>
<td>manual dispatch
</td>
<td>implement stub object, invoke callbacks
</td>
</tr>
<tr>
<td><strong>generated code</strong>
</td>
<td>structures, data tables, inline functions
</td>
<td>structures, data tables, inline functions
</td>
<td>structures, data tables, constructors, destructors, proxies, stubs, inline functions
</td>
</tr>
<tr>
<td><strong>generated code footprint</strong>
</td>
<td>small (data tables only)
</td>
<td>small (data tables only)
</td>
<td>moderate (data tables, constructors, destructors, proxies, and stubs)
</td>
</tr>
<tr>
<td><strong>friendliness</strong>
</td>
<td>surly
</td>
<td>aloof
</td>
<td>genial
</td>
</tr>
</table>
_Point for discussion: given how close the C++ native style is to the C style
(mostly syntactic benefits), we might want to consider deferring its
implementation or finding some other middle ground like in-place friendly
non-POD with weak RAII semantics (don't traverse pointers)._
## Code Generator
### Mapping Declarations
#### Mapping FIDL Types to C++ Types
This is the mapping from FIDL types to C types which the code generator
produces.
<table>
<tr>
<td><strong>FIDL</strong>
</td>
<td><strong>Native C++ Style</strong>
</td>
<td><strong>Idiomatic C++ Style</strong>
</td>
</tr>
<tr>
<td>bool
</td>
<td>bool,
<em>assuming sizeof(bool) ==1</em>
</td>
<td>bool
</td>
</tr>
<tr>
<td>int8
</td>
<td>int8_t
</td>
<td>int8_t
</td>
</tr>
<tr>
<td>uint8
</td>
<td>uint8_t
</td>
<td>uint8_t
</td>
</tr>
<tr>
<td>int16
</td>
<td>int16_t
</td>
<td>int16_t
</td>
</tr>
<tr>
<td>uint16
</td>
<td>uint16_t
</td>
<td>uint16_t
</td>
</tr>
<tr>
<td>int32
</td>
<td>int32_t
</td>
<td>int32_t
</td>
</tr>
<tr>
<td>uint32
</td>
<td>uint32_t
</td>
<td>uint32_t
</td>
</tr>
<tr>
<td>int64
</td>
<td>int64_t
</td>
<td>int64_t
</td>
</tr>
<tr>
<td>uint64
</td>
<td>uint64_t
</td>
<td>uint64_t
</td>
</tr>
<tr>
<td>float32
</td>
<td>float
</td>
<td>float
</td>
</tr>
<tr>
<td>float64
</td>
<td>double
</td>
<td>double
</td>
</tr>
<tr>
<td>handle, handle?
</td>
<td>zx_handle_t, or perhaps zx::unowned_handle
</td>
<td>zx::handle
</td>
</tr>
<tr>
<td>handle<T>, handle<T>?
</td>
<td>zx_handle_t, or perhaps zx::unowned_T
</td>
<td>zx::T <em>(subclass of zx::object<T>)</em>
</td>
</tr>
<tr>
<td>string
</td>
<td>fidl::string
</td>
<td>std::string
</td>
</tr>
<tr>
<td>string?
</td>
<td>fidl::string
</td>
<td>std::optional<std::string>
</td>
</tr>
<tr>
<td>vector<T>
</td>
<td>fidl::vector<T>
</td>
<td>std::vector<T>
</td>
</tr>
<tr>
<td>vector<T>?
</td>
<td>fidl::vector<T>
</td>
<td>std::optional<std::vector>
</td>
</tr>
<tr>
<td>T[N]
</td>
<td>fidl::array<T, N>
</td>
<td>std::array<T, N>
</td>
</tr>
<tr>
<td><em>Interface, Interface?</em>
</td>
<td><em>interface named typedef to zx_handle_t</em>
<p>
<em>eg. typedef zx_handle_t Foo;</em>
</td>
<td><em>Interface</em>Ptr
</td>
</tr>
<tr>
<td><em>Interface&, Interface&?</em>
</td>
<td><em>interface_request named typedef to zx_handle_t</em>
<p>
<em>eg. typedef zx_handle_t FooRequest;</em>
</td>
<td><em>Interface</em>Request
</td>
</tr>
<tr>
<td><em>Struct</em>
</td>
<td>struct <em>Struct</em>
</td>
<td><em>Struct</em>Ptr
</td>
</tr>
<tr>
<td><em>Struct?</em>
</td>
<td>struct <em>Struct*</em>
</td>
<td><em>Struct</em>Ptr
</td>
</tr>
<tr>
<td><em>Union</em>
</td>
<td>struct <em>Union</em>
</td>
<td><em>Union</em>Ptr
</td>
</tr>
<tr>
<td><em>Union?</em>
</td>
<td><em>struct Union*</em>
</td>
<td><em>Union</em>Ptr
</td>
</tr>
<tr>
<td><em>Enum</em>
</td>
<td><em>enum class Foo : data type</em>
</td>
<td><em>enum class Foo : data type</em>
</td>
</tr>
</table>
#### Mapping FIDL Identifiers to C++ Identifiers
TODO: discuss reserved words, name mangling
#### Mapping FIDL Type Declarations to C++ Types
TODO: discuss generated namespaces, constants, enums, typedefs, encoding tables
## Bindings Library
### Dependencies
Only depends on Zircon system headers, libzx, and a portion of the C and C++
standard libraries.
Does not depend on libftl or libmtl.
### Code Style
To be discussed.
The native bindings library uses C++ standard library style, eg. function names
are lower-case with underscores.
The idiomatic bindings library could use Google C++ style to match FIDL v1.0 but
though this may ultimately be more confusing, especially given style choices in
Zircon so we may prefer to follow the C++ standard library style here as well.
### Native Types
#### fidl::string
```
class string {
public:
void init(size_t size, uint8_t* data);
size_t size() const;
bool empty() const;
bool null() const;
uint8_t* data();
const uint8_t* data() const;
uint8_t& operator[](size_t pos);
const uint8_t& operator[](size_t pos) const;
iterator begin();
// etc...
private:
fidl_string string_;
};
```
Holds a reference to a variable-length string stored within the buffer. C++
wrapper of **fidl_string**.
No constructor or destructor so this is POD.
#### fidl::vector<T>
```
template<typename T>
class vector {
public:
void init(size_t size, T* data);
size_t size() const;
bool empty() const;
bool null() const;
T* data();
const T* data() const;
T& operator[](size_t pos);
const T& operator[](size_t pos) const;
iterator begin();
// etc…
private:
fidl_vector vector_;
};
```
Holds a reference to a variable-length vector of elements stored within the
buffer. C++ wrapper of **fidl_vector**.
No constructor or destructor so this is POD.
#### fidl::array<T, N>
```
template<typename T, size_t N>
class array {
public:
size_t size() const;
bool empty() const;
bool null() const;
T* data();
const T* data() const;
T& operator[](size_t pos);
const T& operator[](size_t pos) const;
iterator begin();
// etc…
private:
T[N] array_;
};
```
Holds a reference to a fixed-length array of elements stored within the buffer.
Similar to std::array<T, N> but intended purely for in-place use.
No constructor or destructor so this is POD.
#### fidl::buffer
```
class buffer {
public:
buffer(size_t max_capacity = ZX_MAX_MESSAGE_SIZE);
~buffer();
template<typename T>
T* append();
fidl::string append_string(size_t size);
template<typename T>
fidl::string append_string(const char* text);
fidl::vector<T> append_vector(size_t size);
const uint8_t* data() const;
size_t size() const;
ZX_status_t encode(const fidl_encoding_table* encoding_table,
std::vector<zx_handle_t>* out_handles);
ZX_status_t decode(const fidl_encoding_table* encoding_table,
const std::vector<zx_handle_t>& out_handles);
};
```
Helper for constructing messages laid out in depth-first traversal order.
Generally takes care of the messy pointer arithmetic for building messages
in-place.
TBD: It might be even more convenient to make a more specialized encode_buffer
which knows the encoding table of the message being constructed. Maybe do
something similar for incoming messages.
##### Example
```
ZX_status_t say_hello(
const zx::channel& channel, const char* text, zx::handle token) {
assert(strlen(text) <= MAX_TEXT_SIZE);
fidl::buffer buf();
auto header = buf.append<fidl_message_header>();
header->transaction_id = 1;
header->flags = 0;
header->ordinal = example_Animal_Say_ordinal;
auto args = buf.append<example::Animal::Say_args>();
args->text = buf.append_string(text);
args->token = std::move(token);
std::vector<zx::handle> handles;
ZX_status_t status = buf.encode(example::Animal::Say_args::encoding,
&handles);
if (status == NO_ERROR) {
status = channel.write(0, buf.data(), buf.size(),
reinterpret_cast<const zx_handle_t*>(handles.data()),
handles.size());
}
return status;
}
```
### Idiomatic Types
TODO: adopt main ideas from FIDL 1.0
InterfacePtr<T> / interface_ptr<T>?
InterfaceRequest<T> / interface_req<T>?
async waiter
etc…
## Examples
See also [FIDL 2.0: I/O Sketch](io-sketch.md).
## Suggested API Improvements over FIDL v1
The FIDL v1 API for calling and implementing FIDL interfaces has generally been
fairly effective so we would like to retain most of its structure in the
idiomatic FIDL v2 bindings. However, there are a few areas that could be
improved.
TODO: actually specify the intended API
### Handling Connection Errors
Handling connection errors systematically has been a cause of concern for
clients of FIDL v1 because method result callbacks and connection error
callbacks are implemented by different parts of the client program.
It would be desirable to consider an API which allows for localized handling of
connection errors at the point of method calls (in addition to interface level
connection error handling as before).
See https://fuchsia-review.git.corp.google.com/#/c/23457/ for one example of how
a client would otherwise work around the API deficiency.
One approach towards a better API may be constructed by taking advantage of the
fact that std::function<> based callbacks are always destroyed even if they are
not invoked (such as when a connection error occurs). It is possible to
implement a callback wrapper which distinguishes these cases and allows clients
to handle them more systematically. Unfortunately such an approach may not be
able to readily distinguish between a connection error vs. proxy destruction.
Alternately we could wire in support for multiple forms of callbacks or for
multiple callbacks.
Or we could change the API entirely in favor of a more explicit Promise-style
mechanism.
There are lots of options here...
TBD (please feel free to amend / expand on this)