blob: 83edee20775fc467089ec1a54b8ec6e37839e45b [file] [log] [blame] [view]
# C++ language FIDL tutorial
## About this tutorial
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](../README.md) for details on the
design and implementation of FIDL, as well as the
[instructions for getting and building Fuchsia](/docs/getting_started.md).
# Getting started
We'll use the `echo.fidl` sample that we discussed in the [FIDL Tutorial](README.md)
introduction section, by opening
[//garnet/examples/fidl/services/echo.fidl](/garnet/examples/fidl/services/echo.fidl).
<!-- NOTE: the code snippets here need to be kept up to date manually by
copy-pasting from the actual source code. Please update a snippet
if you notice it's out of date. -->
```fidl
library fidl.examples.echo;
[Discoverable]
protocol Echo {
EchoString(string? value) -> (string? response);
};
```
## Build
You can build the code via the following:
```sh
fx set-petal garnet
fx build
```
### Generated files
Building runs the FIDL compiler automatically.
It writes the glue code that allows the protocols to be used from different languages.
Below are the implementation files created for C++, assuming that your build flavor is `x64`.
```
./out/x64/fidling/gen/fidl/examples/echo/cpp/fidl.cc
./out/x64/fidling/gen/fidl/examples/echo/cpp/fidl.h
```
## `Echo` server
The echo server implementation can be found at:
[//garnet/examples/fidl/echo_server_cpp/](/garnet/examples/fidl/echo_server_cpp/).
Find the implementation of the main function, and that of the `Echo` protocol.
To understand how the code works, here's a summary of what happens in the server
to execute an IPC call.
1. Fuchsia loads the server executable, and your `main()` function starts.
1. `main` creates an `EchoServerApp` object which will bind to the service
protocol when it is constructed.
1. `EchoServerApp()` registers itself with the `StartupContext` by calling
`context->outgoing().AddPublicService<Echo>()`. It passes a lambda function
that is called when a connection request arrives.
1. Now `main` starts the run loop, expressed as an `async::Loop`.
1. The run loop receives a request to connect from another component, so
calls the lambda created in `EchoServerApp()`.
1. That lambda binds the `EchoServerApp` instance to the request channel.
1. The run loop receives a call to `EchoString()` from the channel and
dispatches it to the object bound in the last step.
1. `EchoString()` issues an async call back to the client using
`callback(value)`, then returns to the run loop.
Let's go through the details of how this works.
### File headers
First the namespace definition. This matches the namespace defined in the FIDL
file in its "library" declaration, but that's incidental:
```cpp
namespace echo {
```
Here are the #include files used in the server implementation:
```cpp
#include <fidl/examples/echo/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/zx/channel.h>
#include "lib/component/cpp/startup_context.h"
```
- `fidl.h` contains the generated C++ definition of our `Echo` FIDL protocol.
- `startup_context.h` is used by `EchoServerApp` to expose service implementations.
### main
Most `main()` functions for FIDL components look very similar. They create a
run loop using `async::Loop` or some other construct, and bind service
implementations. The `Loop.Run()` function enters the message loop to process
requests that arrive over channels.
Eventually, another FIDL component will attempt to connect to our component.
### The `EchoServerApp()` constructor
Note that a connection is defined as the _first_ channel with another component.
Any additional channels are not "connections".
Therefore, service registration is performed before the run loop
begins and before the first connection is made.
Here's what the `EchoServerApp` constructor looks like:
```cpp
EchoServerApp()
: context_(fuchsia::sys::ComponentContext::CreateFromStartupInfo()) {
context_->outgoing().AddPublicService<Echo>(
[this](fidl::InterfaceRequest<Echo> request) {
bindings_.AddBinding(this, std::move(request));
});
```
The function calls `AddPublicService` once for each service it makes available
to the other component (remember that each service exposes a single
protocol). The information is cached by `StartupContext` and used to decide
which `Interface` factory to use for additional incoming channels. A new
channel is created every time someone calls `ConnectToService()` on the other
end.
If you read the code carefully, you'll see that the parameter to
`AddPublicService` is actually a lambda function that captures `this`. This
means that the lambda function won't be executed until a channel tries to bind
to the protocol, at which point the object is bound to the channel and will
receive calls from other components. Note that these calls have
thread-affinity, so calls will only be made from the same thread.
The function passed to `AddPublicService` can be implemented in different ways.
The one in `EchoServerApp` uses the same object for all channels. That's a good
choice for this case because the implementation is stateless. Other, more
complex implementations could create a different object for each channel or
perhaps re-use the objects in some sort of caching scheme.
Connections are always point to point. There are no multicast connections.
### The `EchoString()` function
Finally we reach the end of our server discussion. When the message loop
receives a message in the channel to call the `EchoString()` function in the
`Echo` protocol, it will be directed to the implementation below:
```cpp
void EchoString(fidl::StringPtr value, EchoStringCallback callback) override {
printf("EchoString: %s\n", value->data());
callback(std::move(value));
}
```
Here's what's interesting about this code:
- The first parameter to `EchoString()` is a `fidl::StringPtr`. As the name
suggests, a `fidl::StringPtr` can be null. Strings in FIDL are UTF-8.
- The `EchoString()` function returns void because FIDL calls are
asynchronous. Any value we might otherwise return wouldn't have anywhere to
go.
- The last parameter to `EchoString()` is the client's callback function. In
this case, the callback takes a `fidl::StringPtr`.
- `EchoServerApp::EchoString()` returns its response to the client by calling
the callback. The callback invocation is also asynchronous, so the call
often returns before the callback is run in the client.
- Because the callback is async, the callback also returns void.
Any call to a protocol in FIDL is asynchronous. This is a big shift if you are
used to a procedural world where function calls return after the work is
complete. Because it's async, there's no guarantee that the call will ever
actually happen, so your callback may never be called. The remote FIDL
component might close, crash, be busy, etc.
## `Echo` client
Let's take a look at the client implementation:
[//garnet/examples/fidl/echo_client_cpp/](/garnet/examples/fidl/echo_client_cpp/)
The structure of the client is similar to that of the server, with a `main`
function and an `async::Loop`. The difference is that the client immediately
kicks off work once everything is initialized. In contrast, the server does no
work until a connection is accepted.
**Note:** a component can be a client, a server, or both, or many. The
distinction in this example between Client and Server is purely for
demonstration purposes.
Here is the summary of how the client makes a connection to the echo service.
1. The shell loads the client executable and calls `main`.
1. `main()` creates an `EchoClientApp` object to handle connecting to the
server, calls `Start()` to initiate the connection, and then starts the
message loop.
1. In `Start()`, the client calls `context_->launcher()->CreateComponent`
with the url to the server component. If the server component is not
already running, it will be created at this point.
1. Next, the client calls `ConnectToService()` to open a channel to the server
component.
1. `main` calls into `echo_->EchoString(...)` and passes the callback. Because
FIDL IPC calls are async, `EchoString()` will probably return before the
server processes the call.
1. `main` then blocks on a response on the protocol.
1. Eventually, the response arrives, and the callback is called with the
result.
### main
main() in the client is very different from the server, as it's synchronous on
the server response.
```cpp
int main(int argc, const char** argv) {
std::string server_url = "fuchsia-pkg://fuchsia.com/echo_server_cpp#meta/echo_server_cpp.cmx";
std::string msg = "hello world";
for (int i = 1; i < argc - 1; ++i) {
if (!strcmp("--server", argv[i])) {
server_url = argv[++i];
} else if (!strcmp("-m", argv[i])) {
msg = argv[++i];
}
}
async::Loop loop(&kAsyncLoopConfigMakeDefault);
echo::EchoClientApp app;
app.Start(server_url);
app.echo()->EchoString(msg, [&loop](fidl::StringPtr value) {
printf("***** Response: %s\n", value->data());
loop.Quit();
});
return loop.Run();
}
```
### Start
`Start()` is responsible for connecting to the remote `Echo` service.
```cpp
void Start(std::string server_url) {
fuchsia::sys::LaunchInfo launch_info;
launch_info.url = server_url;
launch_info.directory_request = echo_provider_.NewRequest();
context_->launcher()->CreateComponent(std::move(launch_info),
controller_.NewRequest());
echo_provider_.ConnectToService(echo_.NewRequest().TakeChannel(),
Echo::Name_);
}
```
First, `Start()` calls `CreateComponent()` to launch `echo_server`. Then, it
calls `ConnectToService()` to bind to the server's `Echo` protocol. The exact
mechanism is somewhat hidden, but the particular protocol is automatically
inferred from the type of `EchoPtr`, which is a typedef for
`fidl::InterfacePtr<Echo>`.
The second parameter to `ConnectToService()` is the service name.
Next the client calls `EchoString()` in the returned protocol. FIDL protocols
are asynchronous, so the call itself does not wait for `EchoString()` to
complete remotely before returning. `EchoString()` returns void because of the
async behavior.
Since the client has nothing to do until the server response arrives, and is
done working immediately after, `main()` then blocks using `loop.Run()`,
then exits. When the response will arrive, then the callback given to
`EchoString()`, will execute first, then `Run()` will return,
allowing `main()` to return and the program to terminate.
### Run the sample
You can run the Hello World example like this:
```sh
$ run fuchsia-pkg://fuchsia.com/echo_client_cpp#meta/echo_client_cpp.cmx
```
You do not need to specifically run the server because the call to
`CreateComponent()` in the client will automatically launch the server.