目標與激勵
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
類型,而不是從原始管道投放至本機。
技術背景
如何提供協助
挑選工作
搜尋包含 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/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::ServiceDirectory
、sys::OutgoingDirectory
) 使用 HLCPPInterfaceHandle
和InterfaceRequest
類型,因此需要額外轉換為 LLCPP 型別管道。
贊助商
提出問題或查看最新狀態:
- yifeit@google.com
- ianloic@google.com