目標和動機
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
類型,而非從原始管道進行本機轉換。
技術背景
如何提供協助
選擇工作
搜尋包含 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::ServiceDirectory
、sys::OutgoingDirectory
) 使用 HLCPPInterfaceHandle
和InterfaceRequest
類型,因此需要額外轉換為 LLCPP 型別管道。
贊助商
如有任何問題或想瞭解最新進度,請與我們聯絡:
- yifeit@google.com
- ianloic@google.com