低階 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;
};

同樣地,LLCPP 執行階段中先前處理 zx::channel 的所有函式,都已更新為更精確的類型,可編碼通訊協定的方向和種類 (例如:fidl::BindServer)。

不過,大多數使用者程式碼仍使用 zx::channel。由於我們已在 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> 方法,避免建立原始 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::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/元件/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 通訊協定組成另一個通訊協定時,兩者之間沒有「是」關係 (繼承、包含)。這表示當通訊協定 More 組合通訊協定 Less 時,您可能會想使用 fidl::ClientEnd<More> 呼叫函式 void foo(fidl::ClientEnd<Less>),但我們不會在這些型別之間提供隱含轉換。

判斷使用情形安全無虞後,即可透過 fidl::ClientEnd<Less>(more_client_end.TakeChannel()) 手動將一個用戶端轉換為另一個。請盡量在轉換中加上註解,說明轉換安全無虞的原因 (例如 More 不會在 Less 上方新增事件)。

最後一個步驟:製作 CL

上傳變更前,請務必仔細檢查下列三個位置:

接著上傳 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::ServiceDirectorysys::OutgoingDirectory) 使用 HLCPP InterfaceHandleInterfaceRequest 型別,因此需要額外轉換為 LLCPP 型別的管道。

贊助者

如有任何問題或想瞭解最新進度,請與我們聯絡:

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