低階 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::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/元件/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 中,刪除與 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::ServiceDirectorysys::OutgoingDirectory) 使用 HLCPP InterfaceHandleInterfaceRequest 類型,因此需要額外轉換為 LLCPP 型別管道。

贊助商

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

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