新的 C++ 繫結教學課程

本節內容將協助您瞭解如何使用新的 C++ FIDL 繫結。請參閱開始使用,取得逐步指南,瞭解如何設定建構作業並從頭開始編寫簡易用戶端或伺服器。如需有效使用繫結的更多相關指南和建議,請參閱主題。請參閱術語一文,瞭解程式碼中經常出現的名稱和概念,並快速說明如何做出正確選擇。

整體來說,C++ 繫結包含:

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

自然與接線領域物件

繫結支援兩種網域物件類型:natural 類型和 wire 類型:

  • 「natural」類型是針對人體工學最佳化的高階網域物件。
    • 這些類型具有智慧指標的子項。
    • 這些物件使用慣用的 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同步)

Topics

術語

用戶端和伺服器中的 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

同步 (簡稱「同步處理」) 適用於包含回應的 FIDL 呼叫 (雙向呼叫),而表示呼叫已遭封鎖:發出這類呼叫的執行緒在回應傳回之前,不會從呼叫傳回。例如,fidl::WireSyncClient 是所有雙向呼叫都是同步的用戶端。同樣地,fidl::WireClient 也具有 .sync() 存取子,可傳回執行同步呼叫的介面。

單向呼叫沒有回應,因此同步性的概念不適用於這類呼叫。

建議

如果您的程式碼是獨立程式,只會耗用其他元件的功能,請判斷其業務需求所需的並行層級:

  • 如果不是管理大量並行作業,您可以使用同步用戶端,以輕鬆讀取直線邏輯。例如,短執行的指令列工具可能會使用 fidl::SyncClient

  • 如果您的程式碼會管理大量並行作業,則通常可以存取非同步調度器 (async_dispatcher_t*)。在這種情況下,在同步和非同步用戶端與呼叫之間選擇時,最好使用非同步對應項目。例如,偏好使用 fidl::WireClient,而不使用 .sync(),而非 fidl::WireSyncClient.sync()。請特別注意,如果調度工具採用單一執行緒,請勿在調度器執行緒上進行同步呼叫,以免發生死結。

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

如果您的程式碼是其他應用程式正在使用的程式庫,則必須根據使用者的需求,思考是否應公開同步或非同步介面。例如,使用同步用戶端及公開同步介面的程式庫會較難由高度並行的應用程式使用,以排定非同步調度工具的工作。

上述是一般建議,不同的非同步執行階段可能會有各自的建議。

與沒有shared的客戶比較:Shared

當用戶端類型的名稱中含有「shared」(共用) 時,該類型即可對任意執行緒進行繫結和刪除。請參閱執行緒指南中的 SharedClient。其中有沒有「共用」的對應項目 (例如 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 物件會管理記憶體緩衝區集區,並提供有效率的分配方式。它們會廣泛用於傳輸領域物件和線路用戶端與伺服器,以避免產生昂貴的複本。

系統不會將運動場與自然網域物件和相關用戶端與伺服器搭配使用,後者會封裝記憶體配置的相關細節。

您可以使用 fidl::Arena 建立位於該區域的線路網域物件。請參閱「記憶體管理」。

將通訊協定透過驅動程式傳輸與接線網域物件使用時,應使用 fdf::Arena 物件來分配編碼訊息所需的緩衝區。