本節將說明如何使用新的 C++ FIDL 繫結。如需設定建構作業的逐步指南,以及從頭開始編寫簡易用戶端或伺服器的相關資訊,請參閱入門指南。如需更詳細的指南和建議,以便有效使用繫結,請參閱「主題」。請參閱「術語」,瞭解程式碼中經常出現的名稱和概念,以及如何做出正確選擇的簡要說明。
從大處來看,C++ 繫結包含以下項目:
自然和線路網域物件
繫結支援兩種類型的網域物件:自然類型和線路類型:
- 自然類型是高層級的網域物件,可針對人體工學進行最佳化。
- 這些類型會透過智慧指標擁有子項。
- 這些類型會使用慣用的 C++ 類型,例如
std::vector
、std::optional
和std::string
。 - 如果您有名為
fuchsia.my.lib
的 FIDL 程式庫,系統會在fuchsia_my_lib
命名空間中產生類型。
- wire 類型已針對效能和原地解碼進行最佳化。
- 這些是專屬的 C++ 標準版面配置類型,其記憶體版面配置與 FIDL 線路格式相符。
- 離線子項是指向個別緩衝區的無擁有權指標。請參閱「線路網域物件的記憶體擁有權」。
- 如果您有名為
fuchsia.my.lib
的 FIDL 程式庫,系統會在fuchsia_my_lib::wire
命名空間中產生類型。
開始專案時,請預設選擇自然類型,因為這類類型更容易使用,且效能也相當不錯。只有在需要精確控制記憶體配置時,或在關鍵路徑中最佳化邏輯時,才轉換為線路類型。由於線路類型包含不安全的檢視畫面,因此如果使用方式不當,可能會導致使用後釋放和其他記憶體安全性錯誤。
開始使用
- 使用自然和線路網域物件
- 編寫伺服器
- 編寫用戶端 (async或同步)
主題
術語
用戶端和伺服器中的 Wire
與無 Wire
如果用戶端/伺服器 API 名稱中含有 Wire
前置字串,表示該 API 只接受線路類型。否則,API 通常可接受線路和自然類型,或預設為自然類型。舉例來說,fidl::Client
支援使用自然和線路類型進行呼叫,而 fidl::WireClient
則會公開更嚴格的介面,只接受線路類型。
fidl::Server
會以自然類型接收要求,並可能以自然或線路類型傳送回覆。另一方面,fidl::WireServer
會接收以線路類型傳送的要求,並只以線路類型傳送回覆。
為了在傳送端支援線路和自然類型,且不造成函式超載的歧義,線路介面會位於 .wire()
存取子下方。舉例來說,如果有 fidl::Client<MyProtocol> client;
,您可以編寫 client->SomeMethod(natural_type);
來使用自然類型提出要求,以及編寫 client.wire()->SomeMethod(wire_type);
來使用線路類型提出要求。
建議
使用不含 Wire
前置字串的用戶端/伺服器 API。只有在需要確保編譯時只使用線路類型時,才可定義使用 Wire
對應項目的函式簽章,例如 fidl::WireClient
。您也可以依附 fuchsia.my.lib_cpp_wire
目標,而非 fuchsia.my.lib_cpp
GN 目標,這樣一來,您就能只依附繫結的線路部分。
在用戶端中使用 Sync
與不使用 sync
同步 (或簡稱「sync」) 適用於具有回應的 FIDL 呼叫 (雙向呼叫),表示呼叫會阻斷:發出這類呼叫的執行緒會在收到回應前,不會從呼叫中傳回。舉例來說,fidl::WireSyncClient
是用戶端,其中所有雙向呼叫都是同步的。同樣地,fidl::WireClient
也有 .sync()
存取工具,可傳回用於進行同步呼叫的介面。
單向呼叫沒有回應,因此同步性概念不適用於這類呼叫。
建議
如果程式碼是獨立程式,且只會使用其他元件的功能,請根據業務需求決定所需的並行處理層級:
如果不管理大量並行作業,您可以使用同步用戶端,以便閱讀直線邏輯。舉例來說,執行時間較短的指令列工具可能會使用
fidl::SyncClient
。如果程式碼管理大量並行作業,通常會存取非同步調度器 (
async_dispatcher_t*
)。在這種情況下,請選擇非同步的對應項目,而不是同步和非同步的用戶端和呼叫。例如,如果有fidl::WireSyncClient
或.sync()
,請盡量不要透過.sync()
轉換為fidl::WireClient
。特別是,如果調度器是單執行緒的,請勿對調度器執行緒發出同步呼叫,以免發生死結。
如果程式碼是服務 (也就是為其他元件提供功能的元件),則應使用非同步調度器和非同步用戶端,以支援多個使用者所需的並行作業層級。
如果您的程式碼是其他應用程式使用的程式庫,則需要根據使用者需求,更謹慎地考量是否應公開同步或非同步介面。舉例來說,如果程式庫使用同步用戶端並公開同步介面,則在非同步調度器上排程工作時,高並行應用程式將更難使用該程式庫。
以上是一般建議,不同非同步執行階段可能會有更具體的建議。
在用戶端中使用 Shared
與不使用 shared
如果用戶端類型名稱中含有「shared」,則可能會在任意執行緒上繫結及銷毀。請參閱執行緒指南中的 SharedClient
。它會有一個沒有「shared」的對應項目,例如 Client
,必須在調度器執行緒上繫結及銷毀。WireClient
和 WireSharedClient
之間也有類似的關係。
建議
在選擇 Client
和 SharedClient
時,請優先使用 Client
,除非應用程式的執行緒模型或效能需求需要使用多執行緒的用戶端。如要瞭解使用 SharedClient
時的許多注意事項,請參閱執行緒指南。Client
中的額外限制旨在減少記憶體競爭和釋放後使用。舉例來說,如果您的物件都位於同一個單執行緒的異步調度器,您可以使用 Client
。
Then
與 ThenExactlyOnce
在雙向通話中的差異
非同步呼叫有回應時,您可以透過兩種方式指定回呼,以便接收該呼叫的結果:
- 使用
.ThenExactlyOnce(...)
時,系統一律會呼叫回呼一次,並傳送結果。 - 使用
.Then(...)
時,系統會在用戶端物件遭到刪除時,自動捨棄回呼,這類情況適合用於以物件為導向的程式碼。
Then
的動機
在發出非同步雙向呼叫時,執行階段已離開發出呼叫的原始範圍,系統會在稍後將該呼叫的結果傳回應用程式。非同步調度器稍後會叫用您在呼叫時指定的後續邏輯,稱為「continuation」。這表示很容易在物件遭到銷毀後使用,導致記憶體毀損:
// The following snippet shows an example of use-after-free
// occurring in asynchronous two-way calls.
void Foo(fidl::WireClient<MyProtocol>& client) {
bool call_ok;
client->SomeMethod().Then(
// The following lambda function represents the continuation.
[&call_ok] (fidl::WireUnownedResult<SomeMethod>& result) {
// `call_ok` has already gone out of scope.
// This would lead to memory corruption.
call_ok = result.ok();
});
}
當接續動作擷取 this
指標,且該參照物件也擁有用戶端時,就會發生更具潛在危險性的毀損情形。銷毀外部物件 (進而銷毀用戶端) 會導致所有待處理的雙向呼叫失敗。當其繼續執行時,所擷取的 this
指標就會失效。
Then
和 ThenExactlyOnce
都會為雙向通話註冊續接。不過,Then
的設計目的是減少上述資料毀損的情況。具體違規事項如下:
Then
可確保在用戶端遭到刪除之前,提供的接續作業最多只會呼叫一次。如果接續作業只擷取與用戶端相同生命週期的物件 (例如,您的使用者物件擁有用戶端),請選擇Then
。刪除使用者物件會使任何未完成的回呼處於待用狀態。不會使用已釋放的記憶體。另一方面,
ThenExactlyOnce
保證會呼叫接續作業一次。如果用戶端物件遭到銷毀,接續會收到取消錯誤。您必須確保任何參照的物件在繼續執行時仍處於運作狀態,這可能在客戶端物件遭到刪除後的某個未指定時間。如果您必須確切呼叫一次繼續運作,例如與fpromise
完成器或 FIDL 伺服器完成器互動,或是在單元測試期間,請選擇ThenExactlyOnce
。
建議
原則上:
- 如果回呼類似
client_->Foo([this]
,請使用Then
(請注意,client_
是成員變數)。 - 如果回呼類似於
client->Foo([completer]
,或client->Foo([]
,或client->Foo([&]
(單元測試中常見)。- 回呼會擷取弱指標或強指標,
- 使用
ThenExactlyOnce
。
請勿擷取生命週期不同的物件,以免在繼續執行時,只有部分物件處於運作狀態。
Zircon 管道傳輸與驅動程式庫傳輸
FIDL 通訊協定會與 FIDL 定義中指定的對應傳輸相關聯,這項設定會決定可透過通訊協定傳送的資源類型,並可能影響用於傳送及接收訊息的產生 API。C++ 繫結支援兩種傳輸:
Zircon 管道傳輸由端點類型 fidl::ClientEnd<SomeProtocol>
和 fidl::ServerEnd<SomeProtocol>
代表。
驅動程式傳輸會使用 fdf::ClientEnd<SomeProtocol>
和 fdf::ServerEnd<SomeProtocol>
這兩種端點類型。
競技場
Arenas 物件會管理記憶體緩衝區集區,並提供有效的配置。這些類別廣泛用於線路網域物件、線路用戶端和伺服器,以避免複製成本高昂。
不使用 Arena 與自然網域物件和相關的用戶端和伺服器,因為這些物件會封裝記憶體配置的詳細資料。
您可以使用 fidl::Arena
建立位於該競技場的線路網域物件。請參閱記憶體管理。
使用線路網域物件時,如果透過驅動程式傳輸使用通訊協定,應使用 fdf::Arena
物件來分配編碼訊息所需的緩衝區。