低階 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:通訊協定要求管道

常見的做法是建立一組管道端點,並將伺服器端傳送至 通訊協定實作項目我們可以避免建立原始的 Zircon 頻道 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/元件/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);

正在開啟 Service Directory

使用前

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();
}

注意事項:因通訊協定組合而解決類型不符問題

其中沒有「is-a」的指令(繼承、子假設) 關係 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 中,刪除顯示設定中的 與 GN 目標相對應的部分 迴歸原始管道也能輕鬆透過圖表來呈現遷移作業 進度。
  • 如果您確定要遷移的目標是 特定 FIDL 通訊協定的 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