低层 C++ 类型通道迁移

目标和动力

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 类型(通过其公共接口进行),例如 而不是从原始频道在本地投放

技术背景

LLCPP 类型的频道参考文档

如何提供帮助

选择任务

搜索包含字符串 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::WireSyncClientfidl::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::ServiceDirectorysys::OutgoingDirectory)使用 HLCPP InterfaceHandleInterfaceRequest 因此需要额外转换为 LLCPP 类型的渠道。

赞助商

如有疑问或需要更新状态,欢迎与我们联系:

  • yifeit@google.com
  • ianloic@google.com