This tutorial describes how to make client calls and write servers in C using the FIDL InterProcess Communication (IPC) system in Fuchsia.
Refer to the main FIDL page for details on the design and implementation of FIDL, as well as the instructions for getting and building Fuchsia.
The reference section documents the bindings.
We'll use the echo.test.fidl
sample that we discussed in the FIDL concepts doc, by opening //garnet/examples/fidl/services/echo.test.fidl.
library fidl.examples.echo; [Discoverable] protocol Echo { EchoString(string? value) -> (string? response); };
Use the following steps to build:
(@@@ to be completed)
Echo
serverThe example server code is in //garnet/examples/fidl/echo_server_c/echo_server.c:
[01] // Copyright 2018 The Fuchsia Authors. All rights reserved. [02] // Use of this source code is governed by a BSD-style license that can be [03] // found in the LICENSE file. [04] [05] #include <lib/async-loop/loop.h> [06] #include <lib/fdio/fd.h> [07] #include <lib/fdio/fdio.h> [08] #include <lib/fdio/directory.h> [09] #include <lib/svc/dir.h> [10] #include <stdio.h> [11] #include <zircon/process.h> [12] #include <zircon/processargs.h> [13] #include <zircon/status.h> [14] #include <zircon/syscalls.h> [15] [16] static void connect(void* context, const char* service_name, [17] zx_handle_t service_request) { [18] printf("Incoming connection for %s.\n", service_name); [19] // TODO(abarth): Implement echo server once FIDL C bindings are available. [20] zx_handle_close(service_request); [21] } [22] [23] int main(int argc, char** argv) { [24] zx_handle_t directory_request = zx_take_startup_handle(PA_DIRECTORY_REQUEST); [25] if (directory_request == ZX_HANDLE_INVALID) { [26] printf("error: directory_request was ZX_HANDLE_INVALID\n"); [27] return -1; [28] } [29] [30] async_loop_t* loop = NULL; [31] zx_status_t status = [32] async_loop_create(&kAsyncLoopConfigAttachToCurrentThread, &loop); [33] if (status != ZX_OK) { [34] printf("error: async_loop_create returned: %d (%s)\n", status, [35] zx_status_get_string(status)); [36] return status; [37] } [38] [39] async_dispatcher_t* dispatcher = async_loop_get_dispatcher(loop); [40] [41] svc_dir_t* dir = NULL; [42] status = svc_dir_create(dispatcher, directory_request, &dir); [43] if (status != ZX_OK) { [44] printf("error: svc_dir_create returned: %d (%s)\n", status, [45] zx_status_get_string(status)); [46] return status; [47] } [48] [49] status = svc_dir_add_service(dir, "public", "fidl.examples.echo.Echo", NULL, connect); [50] if (status != ZX_OK) { [51] printf("error: svc_dir_add_service returned: %d (%s)\n", status, [52] zx_status_get_string(status)); [53] return status; [54] } [55] [56] status = async_loop_run(loop, ZX_TIME_INFINITE, false); [57] if (status != ZX_OK) { [58] printf("error: async_loop_run returned: %d (%s)\n", status, [59] zx_status_get_string(status)); [60] return status; [61] } [62] [63] svc_dir_destroy(dir); [64] async_loop_destroy(loop); [65] [66] return 0; [67] }
main():
[24
.. 28]
),[30
.. 37]
),[49]
), and finally[56]
).When the async loop returns, we clean up ([63]
and [64]
) and exit.
The connect() function is waiting for
abarth
to implement it ([19]
) :-)
Echo
client(@@@ to be completed)
This section describes the FIDL implementation for C, including the libraries and code generator.
Consult the C Family Comparison document for an overview of the similarities and differences between the C and C++ bindings.
To avoid generating any non-inline code whatsoever, the C language bindings instead produce encoding tables which describe how objects are encoded.
To allow for objects to be introspected (eg. printed), the C language bindings produce introspection tables which describe the name and type signature of each method of each protocol and data structure.
Although small, introspection tables will be stripped out by the linker if unused.
This is the mapping from FIDL types to C types which the code generator produces.
FIDL | C Type |
---|---|
bits | typedef to underlying type |
bool | bool |
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? , handle<T> , handle<T>? | zx_handle_t |
string , string? | fidl_string_t |
vector , vector? | fidl_vector_t |
array<T>:N | T[N] |
protocol , protocol? | typedef to zx_handle_t |
request<I> , request<I>? | typedef to zx_handle_t |
struct | struct Struct |
struct? | struct Struct* |
union | struct Union |
union? | struct Union* |
table | (not supported) |
enum | typedef to underlying type |
The zircon/fidl.h
header defines the basic constructs of the FIDL wire format. The header is part of the Zircon system headers and depends only on other Zircon system headers and a small portion of the C standard library.
typedef struct fidl_message_header { zx_txid_t txid; uint32_t reserved0; uint32_t flags; uint32_t ordinal; } fidl_message_header_t;
Defines the initial part of every FIDL message sent over a channel. The header is immediately followed by the body of the payload. Currently, there are no flags to be set, and so flags
must be zero.
typedef struct fidl_string { // Number of UTF-8 code units (bytes), must be 0 if |data| is null. uint64_t size; // Pointer to UTF-8 code units (bytes) or null char* data; } fidl_string_t;
Holds a reference to a variable-length string.
When decoded, data points to the location within the buffer where the string content lives, or NULL if the reference is null.
When encoded, data is replaced by FIDL_ALLOC_PRESENT when the reference is non-null or FIDL_ALLOC_ABSENT when the reference is null. The location of the string's content is determined by the depth-first traversal order of the message during decoding.
typedef struct fidl_vector { // Number of elements, must be 0 if |data| is null. uint64_t count; // Pointer to element data or null. void* data; } fidl_vector_t;
Holds a reference to a variable-length vector of elements.
When decoded, data points to the location within the buffer where the elements live, or NULL if the reference is null.
When encoded, data is replaced by FIDL_ALLOC_PRESENT when the reference is non-null or FIDL_ALLOC_ABSENT when the reference is null. The location of the vector's content is determined by the depth-first traversal order of the message during decoding.
typedef struct fidl_msg { // The bytes of the message. // // The bytes of the message might be in the encoded or decoded form. // Functions that take a |fidl_msg_t| as an argument should document whether // the expect encoded or decoded messages. // // See |num_bytes| for the number of bytes in the message. void* bytes; // The handles of the message. // // See |num_bytes| for the number of bytes in the message. zx_handle_t* handles; // The number of bytes in |bytes|. uint32_t num_bytes; // The number of handles in |handles|. uint32_t num_handles; } fidl_msg_t;
Represents a FIDL message, including both bytes
and handles
. The message might be in the encoded or decoded format. The ownership semantics for the memory referred to by bytes
and handles
is defined by the context in which the fidl_msg_t
struct is used.
typedef struct fidl_txn fidl_txn_t; struct fidl_txn { // Replies to the outstanding request and complete the FIDL transaction. // // Pass the |fidl_txn_t| object itself as the first parameter. The |msg| // should already be encoded. This function always consumes any handles // present in |msg|. // // Call |reply| only once for each |txn| object. After |reply| returns, the // |txn| object is considered invalid and might have been freed or reused // for another purpose. zx_status_t (*reply)(fidl_txn_t* txn, const fidl_msg_t* msg); };
Represents a outstanding FIDL transaction that requires a reply. Used by the simple C bindings to route replies to the correct transaction on the correct channel.
zx_status_t fidl_encode(const fidl_type_t* type, void* bytes, uint32_t num_bytes, zx_handle_t* handles, uint32_t max_handles, uint32_t* out_actual_handles, const char** out_error_msg); zx_status_t fidl_encode_msg(const fidl_type_t* type, fidl_msg_t* msg, uint32_t* out_actual_handles, const char** out_error_msg);
Declared in lib/fidl/coding.h, defined in encoding.cc.
Encodes and validates exactly num_bytes of the object in bytes in-place by performing a depth-first traversal of the encoding data from type to fix up internal references. Replaces internal pointers references with FIDL_ALLOC_ABSENT
or FIDL_ALLOC_PRESENT
to indicate presence. Extracts non-zero internal handle references out of bytes, stores up to max_handles of them sequentially in handles, and replaces their location in bytes with FIDL_HANDLE_PRESENT
to indicate their presence. Sets out_actual_handles to the number of handles stored in handles.
To prevent handle leakage, this operation ensures that either all handles within bytes are moved into handles in case of success or they are all closed in case of an error.
If a recoverable error occurs, such as encountering a null pointer for a required sub-object, bytes remains in an unusable partially modified state.
All handles in bytes which were already been consumed up to the point of the error are closed and out_actual_handles is set to zero. Depth-first traversal of the object then continues to completion, closing all remaining handles in bytes.
If an unrecoverable error occurs, such as exceeding the bound of the buffer, exceeding the maximum nested complex object recursion depth, encountering invalid encoding table data, or a dangling pointer, the behavior is undefined.
On success, bytes and handles describe an encoded object ready to be sent using zx_channel_send()
.
If anything other than ZX_OK
is returned, error_msg_out will be set.
Result is...
ZX_OK
: successZX_ERR_INVALID_ARGS
:ZX_HANDLE_INVALID
FIDL_RECURSION_DEPTH
was exceeded (see wire format)This function is effectively a simple interpreter of the contents of the type. Unless the object encoding includes internal references which must be fixed up, the only work amounts to checking the object size and the ranges of data types such as enums and union tags.
zx_status_t fidl_decode(const fidl_type_t* type, void* bytes, uint32_t num_bytes, const zx_handle_t* handles, uint32_t num_handles, const char** error_msg_out); zx_status_t fidl_decode_msg(const fidl_type_t* type, fidl_msg_t* msg, const char** out_error_msg);
Declared in lib/fidl/coding.h, defined in decoding.cc.
Decodes and validates the object in bytes in-place by performing a depth-first traversal of the encoding data from type to fix up internal references. Patches internal pointers within bytes whose value is FIDL_ALLOC_PRESENT
to refer to the address of the out-of-line data they reference later in the buffer. Populates internal handles within bytes whose value is FIDL_HANDLE_PRESENT
to their corresponding handle taken sequentially from handles.
To prevent handle leakage, this operation ensures that either all handles in handles from handles[0] to handles[num_handles - 1] are moved into bytes in case of success or they are all closed in case of an error.
The handles array is not modified by the operation.
If a recoverable error occurs, a result is returned, bytes remains in an unusable partially modified state, and all handles in handles are closed.
If an unrecoverable error occurs, such as encountering an invalid type, the behavior is undefined.
If anything other than ZX_OK
is returned, error_msg_out will be set.
Result is...
ZX_OK
: successZX_ERR_INVALID_ARGS
:FIDL_ALLOC_ABSENT
.ZX_HANDLE_INVALID
.FIDL_ALLOC_ABSENT
but has size > 0.FIDL_ALLOC_ABSENT
or FIDL_ALLOC_PRESENT
.ZX_HANDLE_INVALID
or FIDL_HANDLE_PRESENT
.FIDL_RECURSION_DEPTH
was exceeded (see wire format)This function is effectively a simple interpreter of the contents of the type. Unless the object encoding includes internal references which must be fixed up, the only work amounts to checking the object size and the ranges of data types such as enums and union tags.
zx_status_t fidl_validate(const fidl_type_t* type, const void* bytes, uint32_t num_bytes, uint32_t num_handles, const char** error_msg_out); zx_status_t fidl_validate_msg(const fidl_type_t* type, const fidl_msg_t* msg, const char** out_error_msg);
Declared in system/ulib/fidl/include/lib/fidl/coding.h, defined in system/ulib/fidl/validating.cc.
Validates the object in bytes in-place by performing a depth-first traversal of the encoding data from type to fix up internal references. This performs the same validation as fidl_decode(), but does not modify any passed-in data.
The bytes buffer is not modified by the operation.
If anything other than ZX_OK
is returned, error_msg_out will be set.
Result is the same as for fidl_encode() above.
This function is effectively a simple interpreter of the contents of the type. Unless the object encoding includes internal references which must be fixed up, the only work amounts to checking the object size and the ranges of data types such as enums and union tags.
zx_status_t fidl_epitaph_write(zx_handle_t channel, zx_status_t error);
Declared in lib/fidl/epitaph.h, defined in epitaph.c.
This function sends an epitaph with the given error number down the given channel. An epitaph is a special message, with ordinal 0xFFFFFFFF, which contains an error code. The epitaph must be the last thing sent down the channel before it is closed.
The client performs the following operations to send a message through a channel.
For especially simple messages, it may be possible to skip the encoding step altogether (or do it manually).
The client performs the following operations to receive a message through a channel.
For especially simple messages, it may be possible to skip the encoding step altogether (or do it manually).
The C language bindings do not provide any special affordances for closing channels. Per the FIDL specification, an epitaph must be sent as the last message prior to closing a channel. Code should call fidl_epitaph_write() prior to closing a channel.
The C language bindings do not provide any special affordances for dispatching protocol method calls. The client should dispatch manually based on the protocol method ordinal, such as by using a switch statement.
The simple C bindings provide easy-to-use C bindings for a subset of the FIDL language.
In order to generate simple C bindings for a protocol, the protocol must have the [Layout="Simple"]
attribute. This attribute enforces that the protocol, including the types referenced by it, conform to the language subset supported by FIDL.
Specifically, every message in the protocol (including both requests and response) must not have any secondary objects except strings and vectors of handles or primitives (see wire format for a definition of secondary objects). This invariant simplifies the memory ownership semantics. Additionally, all strings and vectors must have explicit non-maximal length bounds. vector<int64>:64
is a vector with such a bound, while vector<int64>
lacks an explicit non-maximal bound. This requirement simplifies buffer management for clients that receive these values.
For example, structs and unions can embed other structs and unions, but they cannot contain nullable references to other structs or unions because nullable structs and unions are stored out-of-line in secondary objects. Nullable handles and protocols are allowed because they're stored inline as ZX_HANDLE_INVALID
.
Below is an example of a protocol that meets these requirements:
library unn.fleet; struct SolarPosition { array<int64>:3 coord; }; enum Alert { GREEN = 1; YELLOW = 2; RED = 3; }; [Layout="Simple"] protocol SpaceShip { AdjustHeading(SolarPosition destination) -> (int8 result); ScanForLifeforms() -> (vector<uint32>:64 life_signs); SetDefenseCondition(Alert alert); };
For clients, the simple C bindings generate a function for each method that takes a channel as its first parameter. These functions are safe to use from any thread and do not require any coordination:
zx_status_t unn_fleet_SpaceShipSetDefenseCondition( zx_handle_t channel, const unn_fleet_Alert* alert);
If the method has a response, the generated function will wait synchronously for the server to reply. If the response contains any data, the data is returned to the caller through out parameters:
zx_status_t unn_fleet_SpaceShipAdjustHeading( zx_handle_t channel, const unn_fleet_SolarPosition* destination, int8_t* result);
The zx_status_t
returned by these functions indicates whether the transport was successful. Protocol-level status is communicated through out parameters.
For servers, the simple C bindings generate an ops table that contains a function pointer for every method in the protocol and a dispatch method that decodes the fidl_msg_t
and calls the appropriate function pointer:
typedef struct unn_fleet_SpaceShip_ops { zx_status_t (*AdjustHeading)(void* ctx, const unn_fleet_SolarPosition* destination, fidl_txn_t* txn); zx_status_t (*ScanForLifeforms)(void* ctx, fidl_txn_t* txn); zx_status_t (*SetDefenseCondition)(void* ctx, const unn_fleet_Alert* alert); } unn_fleet_SpaceShip_ops_t; zx_status_t unn_fleet_SpaceShip_dispatch( void* ctx, fidl_txn_t* txn, fidl_msg_t* msg, const unn_fleet_SpaceShip_ops_t* ops);
The ctx
parameter is an opaque parameter that is passed through the dispatch function to the appropriate function pointer. You can use the ctx
parameter to pass contextual information to the method implementations.
The txn
parameter is passed through the dispatch function to function pointers for methods that have responses. To reply to a message, the implementation of that method should call the appropriate reply function:
zx_status_t unn_fleet_SpaceShipScanForLifeforms_reply( fidl_txn_t* txn, const uint32_t* life_signs_data, size_t life_signs_count);
For example, ScanForLifeforms
might be implemented as follows:
static zx_status_t SpaceShip_ScanForLifeforms(void* ctx, fidl_txn_t* txn) { uint32_t life_signs[4] = {42u, 32u, 79u, 23u}; return unn_fleet_SpaceShipScanForLifeforms_reply(txn, life_signs, 4); }
These reply functions encode the reply and call through the reply
function pointer on fidl_msg_t
.
FIDL also provides fidl_bind
, defined in lib/fidl/bind.h, that binds a generated dispatch function to an async_dispatcher_t
. The fidl_bind
function creates an async_wait_t
that waits for messages on the channel and calls through the given dispatcher (and ops table) when they arrive.