新的 C++ 繫結教學課程

本節將說明如何使用新的 C++ FIDL 繫結。如需設定建構作業的逐步指南,以及從頭開始編寫簡易用戶端或伺服器的相關資訊,請參閱入門指南。如需更詳細的指南和建議,以便有效使用繫結,請參閱「主題」。請參閱「術語」,瞭解程式碼中經常出現的名稱和概念,以及如何做出正確選擇的簡要說明。

從大處來看,C++ 繫結包含以下項目:

  • 資料
  • 行為:用於透過通訊協定傳送這些網域物件的用戶端/伺服器 API、接收事件...

自然和線路網域物件

繫結支援兩種類型的網域物件:自然類型和線路類型:

  • 自然類型是高層級的網域物件,可針對人體工學進行最佳化。
    • 這些類型會透過智慧指標擁有子項。
    • 這些類型會使用慣用的 C++ 類型,例如 std::vectorstd::optionalstd::string
    • 如果您有名為 fuchsia.my.lib 的 FIDL 程式庫,系統會在 fuchsia_my_lib 命名空間中產生類型。
  • wire 類型已針對效能和原地解碼進行最佳化。
    • 這些是專屬的 C++ 標準版面配置類型,其記憶體版面配置與 FIDL 線路格式相符。
    • 離線子項是指向個別緩衝區的無擁有權指標。請參閱「線路網域物件的記憶體擁有權」。
    • 如果您有名為 fuchsia.my.lib 的 FIDL 程式庫,系統會在 fuchsia_my_lib::wire 命名空間中產生類型。

開始專案時,請預設選擇自然類型,因為這類類型更容易使用,且效能也相當不錯。只有在需要精確控制記憶體配置時,或在關鍵路徑中最佳化邏輯時,才轉換為線路類型。由於線路類型包含不安全的檢視畫面,因此如果使用方式不當,可能會導致使用後釋放和其他記憶體安全性錯誤。

開始使用

  1. 使用自然和線路網域物件
  2. 編寫伺服器
  3. 編寫用戶端 (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,必須在調度器執行緒上繫結及銷毀。WireClientWireSharedClient 之間也有類似的關係。

建議

在選擇 ClientSharedClient 時,請優先使用 Client,除非應用程式的執行緒模型或效能需求需要使用多執行緒的用戶端。如要瞭解使用 SharedClient 時的許多注意事項,請參閱執行緒指南Client 中的額外限制旨在減少記憶體競爭和釋放後使用。舉例來說,如果您的物件都位於同一個單執行緒的異步調度器,您可以使用 Client

ThenThenExactlyOnce 在雙向通話中的差異

非同步呼叫有回應時,您可以透過兩種方式指定回呼,以便接收該呼叫的結果:

  • 使用 .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 指標就會失效。

ThenThenExactlyOnce 都會為雙向通話註冊續接。不過,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 物件來分配編碼訊息所需的緩衝區。