目标和动机
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
的类继承的位置。该类是一种补丁,用于将接受 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 协议组合使用另一个 FIDL 协议时,二者之间没有“是”关系(继承、从属)。这意味着,当协议 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
中,删除与 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