目标和动机
FIDL 协议和协议请求在底层由 Zircon 渠道提供支持。假设有以下 FIDL 定义:
library foo;
protocol Calculator {};
resource struct Record {
// Client endpoint of a channel speaking the Calculator protocol
Calculator c;
// Server endpoint of a channel speaking the Calculator protocol
request<Calculator> s;
};
我们过去在 LLCPP 中生成一个包含两个 Zircon 通道的结构体:
struct Record {
zx::channel c;
zx::channel s;
};
任何 FIDL 协议都只是一个通道,这可能会导致意外混淆协议类型或方向(以下是一些已识别 并 已修复的实例)。为了提高类型安全性和自我记录能力,我们将生成的代码更改为以下内容:
struct Record {
// Now it's clear that |c| is a client channel endpoint speaking the |Calculator| protocol.
fidl::ClientEnd<foo::Calculator> c;
// Similarly, |s| is a server channel endpoint for that protocol.
fidl::ServerEnd<foo::Calculator> s;
};
同样,LLCPP 运行时中之前处理 zx::channel 的所有函数都已更新为使用更精确的类型,该类型可对协议的方向和种类进行编码(例如:fidl::BindServer)。
不过,大多数用户代码仍使用 zx::channel。它们之所以继续编译,是因为我们以牺牲类型安全为代价,为 fidl::ClientEnd / fidl::ServerEnd 添加了临时隐式转换支持。为了在整个代码库中获享此更改带来的好处,用户代码应通过其公共接口传播 fidl::ClientEnd / fidl::ServerEnd 类型,而不是从原始信道进行本地类型转换。
技术背景
如何提供帮助
选择任务
搜索包含字符串 TODO(https://fxbug.dev/42148734) 的 BUILD.gn 文件。它将类似于以下内容:
# TODO(https://fxbug.dev/42148734): This target uses raw zx::channel with LLCPP which is deprecated.
# Please migrate to typed channel APIs (fidl::ClientEnd<T>, fidl::ServerEnd<T>).
# See linked bug for details.
configs += [ "//build/cpp:fidl-llcpp-deprecated-raw-channels" ]
移除这些行和 fx build。如果构建成功且没有任何警告或错误,请跳到最后一步。否则,警告和错误会指向已弃用的用法。接下来,我们来看三种典型情况:
场景 1:实现服务器
迁移服务器非常简单 - 查找服务器实现继承自名为 RawChannelInterface 的类的地方。该类是一个 shim,可将采用 fidl::ClientEnd<P> / fidl::ServerEnd<P> 实参的服务器方法转换为采用 zx::channel 实参的方法。将其更改为常规的 Interface,并更新方法实参以匹配:
FIDL
protocol Foo {
TakeBar(Bar bar);
HandleBar(request<Bar> bar);
};
之前
class MyServer : public fidl::WireRawChannelInterface<Foo> {
void TakeBar(zx::channel bar, TakeBarCompleter::Sync& completer) override;
void HandleBar(zx::channel bar, HandleBarCompleter::Sync& completer) override;
};
购买之后
class MyServer : public Foo::Interface {
void TakeBar(fidl::ClientEnd<Bar> bar, TakeBarCompleter::Sync& completer) override;
void HandleBar(fidl::ServerEnd<Bar> bar, HandleBarCompleter::Sync& completer) override;
};
场景 2:协议请求流水线
通常的做法是创建一对通道端点,并将服务器端传递给协议实现。我们可以使用 fidl::CreateEndpoints<Protocol> 方法避免创建原始 Zircon 渠道:
之前
zx::channel client_end, server_end;
zx_status_t status = zx::channel::create(0, &client_end, &server_end);
if (status != ZX_OK)
return status;
foo.HandleBar(std::move(server_end));
fidl::WireClient<Bar> bar(std::move(client_end), &dispatcher);
购买之后
auto bar_ends = fidl::CreateEndpoints<Bar>();
if (!bar_ends.is_ok())
return bar_ends.status_value();
foo.HandleBar(std::move(bar_ends->server));
fidl::WireClient bar(std::move(bar_ends->client), &dispatcher);
// Alternatively, |CreateEndpoints| supports returning the client-end by address,
// which would be useful when the client-end is an instance variable, for example
// in a test fixture.
fidl::ClientEnd<Foo> bar_client_end;
auto bar_server_end = fidl::CreateEndpoints(&bar_client_end);
if (!bar_server_end.is_ok())
return bar_server_end.status_value();
foo.HandleBar(std::move(*bar_server_end));
请注意,使用类型化渠道时,可以省略 fidl::WireClient 的协议模板参数,从而使代码更加简洁。
同步客户端
您可以使用 fidl::WireSyncClient 将 fidl::ClientEnd 转换为相应协议的同步客户端。这样做的好处是,避免了必须两次拼写出协议类型(一次在 ClientEnd 中,另一次在同步客户端类中)。
fidl::WireSyncClient bar{std::move(bar_ends->client)};
场景 3:连接到协议
fdio_service_connect 通常用于连接到组件命名空间中的 FIDL 服务。由于其签名是 C,因此使用起来非常冗长,尤其是在存在类型化渠道的情况下。我们创建了符合人体工程学的封装容器:component::Connect<Protocol>、component::ConnectAt<Protocol> 和 component::OpenServiceRoot。它们位于 sdk/lib/sys/component/cpp 库中。
连接到单个协议
之前
zx::channel client_end, server_end;
zx_status_t status = zx::channel::create(0, &client_end, &server_end);
if (status != ZX_OK)
return status;
status = fdio_service_connect("/svc/fuchsia.Foo", server_end.release());
if (status != ZX_OK)
return status;
fidl::WireClient<Foo> foo(std::move(client_end), &dispatcher);
购买之后
// The channel creation and service connection is done in one function.
// By default it opens the protocol name.
// Returns |zx::result<fidl::ClientEnd<Foo>>|.
auto client_end = component::Connect<Foo>();
if (!client_end.is_ok())
return client_end.status_value();
// Note: can omit template argument
fidl::WireClient foo(std::move(*client_end), &dispatcher);
打开服务目录
之前
zx::channel client_end, server_end;
zx_status_t status = zx::channel::create(0, &client_end, &server_end);
if (status != ZX_OK)
return status;
status = fdio_service_connect("/svc", server_end.release());
if (status != ZX_OK)
return status;
fidl::WireClient<::fuchsia_io::Directory> dir(std::move(client_end));
购买之后
// The channel creation and service connection is done in one function.
// Opens "/svc" and returns the client endpoint, as a
// |zx::result<fidl::ClientEnd<::fuchsia_io::Directory>>|.
auto client_end = component::OpenServiceRoot();
if (!client_end.is_ok())
return client_end.status_value();
// Note: can omit template argument
fidl::WireClient dir(std::move(*client_end), &dispatcher);
注意:传播协议类型
在可行的情况下,最好在相关函数和变量之间传播协议类型。每当您发现自己要从某个频道创建 ClientEnd/ServerEnd / UnownedClientEnd 时,请考虑是否也可以将源频道更改为类型化频道。它们可作为自我检查文档,并可揭示有关流经渠道的协议类型的不正确假设。与 LLCPP 生成的结构不同,在公共 API 上使用类型化通道不会使接口不利地倾向于特定的所有权模型或类型集,因为类型化通道只是 Zircon 通道的轻量级封装容器。下面展示了一个迁移 zx::unowned_channel 的示例:
之前
// |client| should speak the |fuchsia.foobar/Baz| protocol.
zx_status_t DoThing(zx::unowned_channel client, int64_t args) {
return fidl::WireCall<fuchsia_foobar::Baz>(std::move(client))->Method(args).status();
}
购买之后
// The intended protocol is encoded in the type system. No need for comment.
zx_status_t DoThing(fidl::UnownedClientEnd<fuchsia_foobar::Baz> client, int64_t args) {
return fidl::WireCall(client)->Method(args).status();
}
注意:解决因协议组合而导致的类型不匹配问题
当一个 FIDL 协议组合另一个协议时,它们之间没有“is-a”(继承、包含)关系。这意味着,当协议 More 组合协议 Less 时,您可能希望使用 fidl::ClientEnd<More> 调用函数 void
foo(fidl::ClientEnd<Less>),但我们不会提供这些类型之间的隐式转换。
在确定使用情况安全后,可以通过 fidl::ClientEnd<Less>(more_client_end.TakeChannel()) 手动将一个客户端端点转换为另一个客户端端点。最好在转化中添加注释,说明其安全性(例如,More 不会在 Less 的基础上添加新事件)。
最后一步:制作 CL
在上传更改之前,请务必仔细检查以下三个位置:
- 已从特定于目标的
BUILD.gn文件中移除"//build/cpp:fidl-llcpp-deprecated-raw-channels"配置。 - 在
//build/cpp/BUILD.gn中,删除 visibility 部分中与您的 GN 目标对应的行,这样就不会回归到原始渠道。此外,您还可以轻松直观地了解迁移进度。 - 如果您确定要迁移的目标是特定 FIDL 协议的
RawChannelInterface的最后一个用户,则可以从fidlgen_cpp编译器中删除该 协议。别担心,如果您过早移除了代码,代码将无法编译。
然后,您可以上传 CL 并使用 Bug: 69585 🎉 对其进行标记
如果需要 FIDL 团队进行特定审核,您可以添加 ianloic@ 或 yifeit@。
示例 CL
迁移过程中发现的已知痛点:
- 在转换
fdio_open3(path, flags, server.release())时,没有fdio_open3的类型安全替代方案。 - 在 HLCPP 和 LLCPP 端点类型之间进行转换非常棘手。我们希望
fidl::ClientEnd<::my_thing::Protocol>和fidl::InterfaceHandle<my::thing::Protocol>能够轻松相互转换,服务器也是如此。 - HLCPP 和旧版组件框架 API(
sys::ServiceDirectory、sys::OutgoingDirectory)使用 HLCPPInterfaceHandle和InterfaceRequest类型,因此需要额外转换为 LLCPP 类型化通道。
赞助商
如有疑问或需要了解最新状态,请与我们联系:
- yifeit@google.com
- ianloic@google.com