新的 C++ 繫結教學課程

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

大致來說,C++ 繫結包含:

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

自然和線路網域物件

繫結支援兩種網域物件:自然型別和連線型別:

  • 自然類型是高階網域物件,可針對人體工學進行最佳化。
    • 這些型別會透過智慧指標擁有自己的子項。
    • 這些函式會使用慣用的 C++ 型別,例如 std::vectorstd::optionalstd::string
    • 假設 FIDL 程式庫名為 fuchsia.my.lib,則型別會產生於 fuchsia_my_lib 命名空間。
  • wire 類型已針對效能和就地解碼進行最佳化。
    • 這些是專門的 C++ 標準版面配置型別,記憶體版面配置與 FIDL 線路格式一致。
    • 行外子項是不屬於任何指標的指標,指向另一個緩衝區。 請參閱「線路網域物件的記憶體擁有權」。
    • 假設 FIDL 程式庫名為 fuchsia.my.lib,則型別會產生於 fuchsia_my_lib::wire 命名空間。

開始專案時,請預設選擇自然類型,因為這類型的使用難度較低,且效能合理。只有在最佳化重要路徑中的邏輯,或需要精確控制記憶體配置時,才使用線路型別。由於線路型別包含不安全的檢視區塊,不當使用這些檢視區塊可能會導致釋放後使用 (use-after-free) 和其他記憶體安全錯誤。

開始使用

  1. 使用自然和線路網域物件
  2. 編寫伺服器
  3. 編寫用戶端 (非同步同步)

主題

術語

用戶端和伺服器中的 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);,使用連線類型提出要求。

建議

使用用戶端/伺服器 API,無須加上 Wire 前置字串。只有在需要確保編譯時只使用線路型別時,才能定義使用 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::WireClient,而非 fidl::WireSyncClient.sync().sync()特別是如果分派器是單一執行緒,請勿在分派器執行緒上發出同步呼叫,以免發生死結。

如果您的程式碼是服務 (即為其他元件提供功能的元件),則應使用非同步調度器和非同步用戶端,支援多個消費者所需的並行層級。

如果您的程式碼是其他應用程式使用的程式庫,則需要更仔細地思考是否應公開同步或非同步介面,具體取決於使用者的需求。舉例來說,如果程式庫使用同步用戶端並公開同步介面,則在非同步調度器上排定工作的並行應用程式,使用起來會更加困難。

以上是一般建議,不同的非同步執行階段可能有更具體的建議。

Shared 與沒有 shared 的客戶

如果用戶端類型名稱中含有「shared」,則可能會在任意執行緒上繫結及毀損。請參閱執行緒指南中的 SharedClient。它會對應到沒有「shared」的項目,例如 Client,必須在調度器執行緒上繫結並銷毀。WireClientWireSharedClient 之間也有類似關係。

建議

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

ThenThenExactlyOnce在雙向通話中的比較

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

  • 使用 .ThenExactlyOnce(...) 時,系統一律只會呼叫回呼一次,並傳回結果。
  • 使用 .Then(...) 時,如果用戶端物件遭到毀損,回呼會遭到捨棄,適合用於物件導向程式碼。

Then」的動機

進行非同步雙向呼叫時,系統會在執行作業離開原始呼叫範圍後,將該呼叫的結果傳回應用程式。非同步調度器稍後會叫用您在進行呼叫時指定的後續邏輯,也就是「續傳」。這表示物件毀損後仍可輕鬆使用,導致記憶體損毀:

// 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 物件分配編碼訊息所需的緩衝區。