目标和动力
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;
};
同样,LLPP 运行时中之前处理
zx::channel
已更新为更精确地对
协议的方向和类型(例如:
fidl::BindServer
)。
不过,大多数用户代码仍然使用 zx::channel
。他们继续
因为我们在 Android Studio 中
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>
方法:
之前
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<Foo>();
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 生成的结构不同,使用
类型化通道不会使接口
因为区分类型的频道
只是针对 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 协议。这意味着,当协议 More
时,
编写协议 Less
,可能想要使用 fidl::ClientEnd<More>
调用函数 void
foo(fidl::ClientEnd<Less>)
,但我们
提供这些类型之间的隐式转换。
确定使用安全后,您可以手动转换
连接到另一个客户端
fidl::ClientEnd<Less>(more_client_end.TakeChannel())
。更喜欢在
转化并说明为何是安全的(例如,More
不会在
(位于 Less
的顶部)。
最后一步:创建 CL
在上传所做更改之前,请务必仔细检查以下三个位置:
"//build/cpp:fidl-llcpp-deprecated-raw-channels"
配置已从以下实例中移除: 特定于目标的BUILD.gn
文件。- 在
//build/cpp/BUILD.gn
中,删除 visibility 中的行 部分,以免其 回归原始通道。您还可以轻松直观地了解 进度。 - 如果您确定要迁移的目标是
RawChannelInterface
,则可以删除该协议 来自fidlgen_cpp
编译器的协议。别担心, 否则代码将无法编译。
然后,您可以上传 CL 并使用 Bug: 69585
进行标记 🎉?
如果需要 FIDL 团队的具体审核,您可以添加 ianloic@、yifeit@ 之一。
CL 示例
在迁移过程中发现的已知痛点:
- 转换
fdio_get_service_handle
时,该函数接受以下输出参数:zx_handle_t
,没有任何协议类型。我们想要fidl::ClientEnd<T>
。 - 转换
fdio_open(path, flags, server.release())
时,fdio_open
的类型安全的替代方案。 - 在 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