This guide explains how to export a FIDL service from a parent driver and utilize it in a child driver. This guide assumes familiarity with the following concepts:
For a complete working example in the Fuchsia source tree, see the Zircon transport example.
FIDL Protocol Definition
The following snippets utilize this FIDL definition:
library fidl.examples.echo;
const MAX_STRING_LENGTH uint64 = 32;
@discoverable
closed protocol Echo {
/// Returns the input.
strict EchoString(struct {
value string:<MAX_STRING_LENGTH, optional>;
}) -> (struct {
response string:<MAX_STRING_LENGTH, optional>;
});
};
service EchoService {
echo client_end:Echo;
};
Parent Driver (The Server)
The parent driver implements the fidl.examples.echo/Echo protocol, serves
fidl.examples.echo.EchoService in its outgoing directory, and offers that
service when adding a child node.
Driver Manifest
In the parent driver's manifest (.dml), declare and expose the FIDL service
capability:
capabilities: [
{ service: "fidl.examples.echo.EchoService" },
],
expose: [
{
service: "fidl.examples.echo.EchoService",
from: "self",
},
],
Server Code
#include <fidl/fidl.examples.echo/cpp/wire.h>
#include <lib/driver/component/cpp/driver_base2.h>
#include <lib/driver/component/cpp/driver_export2.h>
#include <lib/driver/component/cpp/node_add_args.h>
#include <lib/driver/logging/cpp/logger.h>
// This class implements the fidl.examples.echo/Echo FIDL protocol using the
// C++ Wire bindings.
class ParentDriver : public fdf::DriverBase2,
public fidl::WireServer<fidl_examples_echo::Echo> {
public:
ParentDriver() : fdf::DriverBase2("parent") {}
zx::result<> Start(fdf::DriverContext context) override {
// Add the FIDL service we wish to export to our child to our outgoing
// directory. When a connection is attempted we bind the server end of
// the channel pair to our server implementation using the driver's default
// dispatcher.
fidl_examples_echo::EchoService::InstanceHandler handler({
.echo = bindings_.CreateHandler(this, dispatcher(),
fidl::kIgnoreBindingClosure),
});
zx::result result =
outgoing()->AddService<fidl_examples_echo::EchoService>(std::move(handler));
if (result.is_error()) {
fdf::error("Failed to add EchoService to outgoing directory: {}", result);
return result.take_error();
}
// Declare our outgoing service offer and add the child node. The driver
// framework uses this offer to route the service to the child and
// automatically populate node properties for binding.
std::vector<fuchsia_driver_framework::NodeProperty2> properties = {};
zx::result child_result = AddChild(
"echo_child", properties,
std::array{fdf::MakeOffer2<fidl_examples_echo::EchoService>()});
if (child_result.is_error()) {
fdf::error("Failed to add child node: {}", child_result);
return child_result.take_error();
}
controller_.Bind(std::move(child_result.value()), dispatcher());
return zx::ok();
}
private:
// Implementation of the method required by our FIDL protocol.
void EchoString(EchoStringRequestView request,
EchoStringCompleter::Sync& completer) override {
completer.Reply(request->value);
}
fidl::WireClient<fuchsia_driver_framework::NodeController> controller_;
// Ensures that the FIDL connections do not outlive the driver instance.
fidl::ServerBindingGroup<fidl_examples_echo::Echo> bindings_;
};
FUCHSIA_DRIVER_EXPORT2(ParentDriver);
Child Driver (The Client)
Driver Manifest
In the child driver's manifest (.dml), declare that the driver uses the
service:
use: [
{ service: "fidl.examples.echo.EchoService" },
],
Note that the child driver's bind rules are automatically generated from its DML.
Client Code
The following code snippet shows how a child driver that has bound to the parent
node connects to and calls the Echo protocol:
#include <fidl/fidl.examples.echo/cpp/wire.h>
#include <lib/driver/component/cpp/driver_base2.h>
#include <lib/driver/component/cpp/driver_export2.h>
#include <lib/driver/logging/cpp/logger.h>
class ChildDriver : public fdf::DriverBase2 {
public:
ChildDriver() : fdf::DriverBase2("child") {}
zx::result<> Start(fdf::DriverContext context) override {
// Connect to the protocol offered by our parent driver through our incoming
// namespace. We do not need to specify the service or protocol name string
// because Connect is templated on the generated service member type.
zx::result client_end =
context.incoming().Connect<fidl_examples_echo::EchoService::Echo>();
if (client_end.is_error()) {
fdf::error("Failed to connect to Echo protocol: {}", client_end);
return client_end.take_error();
}
// Turn the client side of the endpoint pair into a synchronous client.
fidl::WireSyncClient client{std::move(client_end.value())};
// Utilize our client to make calls.
constexpr std::string_view kInput = "Test String";
fidl::WireResult result =
client->EchoString(fidl::StringView::FromExternal(kInput));
if (!result.ok()) {
fdf::error("Failed to call EchoString: {}", result.error());
return zx::error(result.status());
}
if (result->response.get() != kInput) {
fdf::error("Unexpected response: Actual: \"{}\", Expected: \"{}\"",
result->response.get(), kInput);
return zx::error(ZX_ERR_INTERNAL);
}
return zx::ok();
}
};
FUCHSIA_DRIVER_EXPORT2(ChildDriver);