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

我們之前使用 LLC 在兩個 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。由於我們已在保護類型安全的情況下,為 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/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 產生的結構不同,在公用 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/cpp:fidl-llcpp-deprecated-raw-channels" 設定已從目標專屬的 BUILD.gn 檔案中移除。
  • //build/cpp/BUILD.gn 中,刪除與 GN 目標對應的瀏覽權限部分中的行,避免程式碼迴歸為原始管道。還能輕鬆以視覺化方式呈現遷移進度。
  • 如果您確定要遷移的目標是特定 FIDL 通訊協定 RawChannelInterface 的最後一位使用者,可以將該通訊協定從 fidlgen_cpp 編譯器中刪除。請別擔心,若是提前移除,程式碼就不會編譯。

接著,您可以上傳 CL,並標記 Bug: 69585 🎉?

如需 FIDL 團隊的特定審查,您可以新增 ianloic@、yifeit@。

CL 範例

遷移期間發現的已知問題點:

  • 轉換 fdio_get_service_handle 時,函式會採用 zx_handle_t 的 out 參數,不含任何通訊協定類型。我們希望 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