實作 C++ FIDL 用戶端

必要條件

本教學課程是以 FIDL 伺服器教學課程為基礎。如需完整的 FIDL 教學課程,請參閱總覽

總覽

本教學課程會實作 FIDL 通訊協定的用戶端,並透過在上一個教學課程中建立的伺服器執行該用戶端。本教學課程中的用戶端是非同步的。另外還有適用於同步用戶端的替代教學課程

用戶端範例的結構

本教學課程隨附的範例程式碼位於 Fuchsia 結帳頁面中的 //examples/fidl/cpp/client。其中包含用戶端元件和其包含的套件。如要進一步瞭解如何建構元件,請參閱「建構元件」。

為了啟動及執行用戶端元件,//examples/fidl/cpp/client/BUILD.gn 中定義的三個目標:

  1. 用戶端的原始可執行檔。這會產生具有指定輸出名稱的二進位檔,且可在 Fuchsia 上執行:

    executable("bin") {
      output_name = "fidl_echo_cpp_client"
      sources = [ "main.cc" ]
    
      deps = [
        "//examples/fidl/fuchsia.examples:fuchsia.examples_cpp",
    
        # This library is used to log messages.
        "//sdk/lib/syslog/cpp",
    
        # This library provides an asynchronous event loop implementation.
        "//zircon/system/ulib/async-loop:async-loop-cpp",
    
        # This library is used to consume capabilities, e.g. protocols,
        # from the component's incoming directory.
        "//sdk/lib/component/incoming/cpp",
      ]
    }
    
    
  2. 設定為執行用戶端執行檔的元件。元件是指軟體在 Fuchsia 上執行的單位。元件可透過其資訊清單檔案來描述。在這種情況下,meta/client.cml 會將 echo-client 設為可執行元件,並在 :bin 中執行 fidl_echo_cpp_client

    fuchsia_component("echo-client") {
      component_name = "echo_client"
      manifest = "meta/client.cml"
      deps = [ ":bin" ]
    }
    
    

    伺服器元件資訊清單位於 //examples/fidl/cpp/client/meta/client.cml。資訊清單中的二進位檔名稱必須與 BUILD.gn 中定義的 executable 輸出名稱相符。

    {
        include: [ "syslog/client.shard.cml" ],
    
        // Information about the program to run.
        program: {
            // Use the built-in ELF runner.
            runner: "elf",
    
            // The binary to run for this component.
            binary: "bin/fidl_echo_cpp_client",
        },
    
        // Capabilities used by this component.
        use: [
            { protocol: "fuchsia.examples.Echo" },
        ],
    }
    
    
  3. 元件隨後會放入套件中,也就是 Fuchsia 上的軟體發布單位。

    # C++ async client and server example package
    fuchsia_package("echo-cpp-client") {
      deps = [
        ":echo-client",
        "//examples/fidl/cpp/server:echo-server",
        "//examples/fidl/echo-realm:echo_realm",
      ]
    }
    
    

建立客戶

  1. 將用戶端新增至建構設定。這個步驟只需要執行一次:

    fx set core.x64 --with //examples/fidl/cpp/client
    
  2. 建構用戶端:

    fx build examples/fidl/cpp/client
    

連線至通訊協定

fuchsia.examples/Echo

int main(int argc, const char** argv) {
  // Connect to the |fuchsia.examples/Echo| protocol inside the component's
  // namespace. This can fail so it's wrapped in a |zx::result| and it must be
  // checked for errors.
  zx::result client_end = component::Connect<fuchsia_examples::Echo>();
  if (!client_end.is_ok()) {
    FX_LOGS(ERROR) << "Synchronous error when connecting to the |Echo| protocol: "
                   << client_end.status_string();
    return -1;
  }

  // ...

同時,元件管理員會將要求轉送至伺服器元件。伺服器教學課程中實作的伺服器處理常式會使用伺服器端點呼叫,並將管道繫結至伺服器實作。

請特別注意,此程式碼假設元件的命名空間已包含 Echo 通訊協定的執行個體。

初始化事件迴圈

非同步用戶端需要 async_dispatcher_t*,才能以非同步方式監控來自管道的訊息。async::Loop 提供由事件迴圈支援的調度工具實作。

  // As in the server, the code sets up an async loop so that the client can
  // listen for incoming responses from the server without blocking.
  async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
  async_dispatcher_t* dispatcher = loop.dispatcher();

調度器可用來執行一部分非同步程式碼。系統會先用於執行 EchoString 方法,並在收到回應時結束。然後,它會在呼叫 SendString 後執行以監聽事件,並在收到 OnString 事件時結束。在這兩個執行個體之間呼叫 ResetQuit() 可讓用戶端重複使用迴圈。

初始化用戶端

如要向伺服器發出 Echo 要求,請使用上一步的用戶端端點、迴圈調度器和事件處理常式委派來初始化用戶端:

  // Define the event handler implementation for the client.
  //
  // The event handler delegate should be an object that implements the
  // |fidl::AsyncEventHandler<Echo>| virtual class, which has methods
  // corresponding to the events offered by the protocol. By default those
  // methods are no-ops.
  class EventHandler : public fidl::AsyncEventHandler<fuchsia_examples::Echo> {
   public:
    void OnString(fidl::Event<::fuchsia_examples::Echo::OnString>& event) override {
      FX_LOGS(INFO) << "(Natural types) got event: " << event.response();
      loop_.Quit();
    }

    // One may also override the |on_fidl_error| method, which is called
    // when the client encounters an error and is going to teardown.
    void on_fidl_error(fidl::UnbindInfo error) override { FX_LOGS(ERROR) << error; }

    explicit EventHandler(async::Loop& loop) : loop_(loop) {}

   private:
    async::Loop& loop_;
  };

  // Create an instance of the event handler.
  EventHandler event_handler{loop};

  // Create a client to the Echo protocol, passing our |event_handler| created
  // earlier. The |event_handler| must live for at least as long as the
  // |client|. The |client| holds a reference to the |event_handler| so it is a
  // bug for the |event_handler| to be destroyed before the |client|.
  fidl::Client client(std::move(*client_end), dispatcher, &event_handler);

撥打 FIDL 呼叫

執行 FIDL 呼叫的方法會公開在反參照運算子後方,因此 FIDL 呼叫看起來像 client->EchoString(...)

非同步 EchoString 呼叫會接受要求物件,並接受透過呼叫結果叫用的回呼,指出成功或失敗:

  client->EchoString({"hello"}).ThenExactlyOnce(
      [&](fidl::Result<fuchsia_examples::Echo::EchoString>& result) {
        // Check if the FIDL call succeeded or not.
        if (!result.is_ok()) {
          // If the call failed, log the error, and crash the program.
          // Production code should do more graceful error handling depending
          // on the situation.
          FX_LOGS(ERROR) << "EchoString failed: " << result.error_value();
          ZX_PANIC("%s", result.error_value().FormatDescription().c_str());
        }
        // Dereference (->) the result object to access the response payload.
        FX_LOGS(INFO) << "(Natural types) got response: " << result->response();
        loop.Quit();
      });
  // Run the dispatch loop, until we receive a reply, in which case the callback
  // above will quit the loop, breaking us out of the |Run| function.
  loop.Run();
  loop.ResetQuit();

您也可以使用自然結構資料表支援的指定初始化樣式雙括號語法:

  client->EchoString({{.value = "hello"}})
      .ThenExactlyOnce(
        // ... callback ...

單向 SendString 呼叫沒有回覆,因此不需要回呼。傳回的結果代表傳送要求時發生任何錯誤。

  // Make a SendString one way call with natural types.
  fit::result<::fidl::Error> result = client->SendString({"hello"});
  if (!result.is_ok()) {
    FX_LOGS(ERROR) << "SendString failed: " << result.error_value();
    ZX_PANIC("%s", result.error_value().FormatDescription().c_str());
  }

使用有線網域物件發出呼叫

上述教學課程說明如何使用自然網域物件進行用戶端呼叫:每個呼叫都會耗用以自然網域物件代表的要求訊息,並傳回同樣在自然網域物件中的返回回覆。針對效能和堆積分配進行最佳化時,可使用網域物件發出呼叫。方法是在呼叫時使用的解除參照運算子之前插入 .wire(),例如 client.wire()->EchoString(...)

使用以下線路類型建立 EchoString 雙向通話:

  client.wire()->EchoString("hello").ThenExactlyOnce(
      [&](fidl::WireUnownedResult<fuchsia_examples::Echo::EchoString>& result) {
        if (!result.ok()) {
          FX_LOGS(ERROR) << "EchoString failed: " << result.error();
          ZX_PANIC("%s", result.error().FormatDescription().c_str());
          return;
        }
        fidl::WireResponse<fuchsia_examples::Echo::EchoString>& response = result.value();
        std::string reply(response.response.data(), response.response.size());
        FX_LOGS(INFO) << "(Wire types) got response: " << reply;
        loop.Quit();
      });

使用以下電匯類型建立 SendString 單向通話:

  fidl::Status wire_status = client.wire()->SendString("hello");
  if (!wire_status.ok()) {
    FX_LOGS(ERROR) << "SendString failed: " << result.error_value();
    ZX_PANIC("%s", result.error_value().FormatDescription().c_str());
  }

有線用戶端呼叫使用的相關類別和函式,與自然用戶端呼叫中使用的類別相似。呼叫其他類別或函式時,線路對應項目通常會加上 Wire 前置字串。指標與參照和引數結構也有差異:

  • 接受自然網域物件的 EchoString 方法可接受單一引數,做為要求網域物件:

    client->EchoString({{.value = "hello"}})
        .ThenExactlyOnce(
    

    如果要求酬載是結構,EchoString 方法會將要求主體中的結構體欄位清單扁平化為個別引數 (這裡是一個 fidl::StringView 引數):

    client.wire()->EchoString("hello").ThenExactlyOnce(
    
  • 非同步自然呼叫中的回呼接受 fidl::Result<Method>&

    client->EchoString({"hello"}).ThenExactlyOnce(
        [&](fidl::Result<fuchsia_examples::Echo::EchoString>& result) {
          // Check if the FIDL call succeeded or not.
          if (!result.is_ok()) {
            // If the call failed, log the error, and crash the program.
            // Production code should do more graceful error handling depending
            // on the situation.
            FX_LOGS(ERROR) << "EchoString failed: " << result.error_value();
            ZX_PANIC("%s", result.error_value().FormatDescription().c_str());
          }
          // Dereference (->) the result object to access the response payload.
          FX_LOGS(INFO) << "(Natural types) got response: " << result->response();
          loop.Quit();
        });
    // Run the dispatch loop, until we receive a reply, in which case the callback
    // above will quit the loop, breaking us out of the |Run| function.
    loop.Run();
    loop.ResetQuit();
    
    • 如要檢查成功或錯誤,請使用 is_ok()is_error() 方法。
    • 如要在之後存取回應酬載,請使用 value()->
    • 您可以移出結果或酬載,因為這些類型都會實作階層物件擁有權。

    非同步線路呼叫的回呼接受 fidl::WireUnownedResult<Method>&

    client.wire()->EchoString("hello").ThenExactlyOnce(
        [&](fidl::WireUnownedResult<fuchsia_examples::Echo::EchoString>& result) {
          if (!result.ok()) {
            FX_LOGS(ERROR) << "EchoString failed: " << result.error();
            ZX_PANIC("%s", result.error().FormatDescription().c_str());
            return;
          }
          fidl::WireResponse<fuchsia_examples::Echo::EchoString>& response = result.value();
          std::string reply(response.response.data(), response.response.size());
          FX_LOGS(INFO) << "(Wire types) got response: " << reply;
          loop.Quit();
        });
    
    • 如要檢查是否成功,請使用 ok() 方法。
    • 如要在之後存取回應酬載,請使用 value()->
    • 您必須在回呼中同步使用結果。結果類型為「未擁有」,代表只會借用 FIDL 執行階段在其他其他位置分配的回應。
  • 單向呼叫也會在自然案例中採用整個要求網域物件,並在電線案例中將要求結構欄位扁平化為單獨的引數:

    // Make a SendString one way call with natural types.
    fit::result<::fidl::Error> result = client->SendString({"hello"});
    
    fidl::Status wire_status = client.wire()->SendString("hello");
    

執行用戶端

如要讓用戶端和伺服器使用 Echo 通訊協定進行通訊,元件架構必須將 fuchsia.examples.Echo 能力從伺服器轉送至用戶端。

  1. 設定您的版本以納入包含 echo 領域、伺服器和用戶端的套件:

    fx set core.x64 --with //examples/fidl/cpp/client
    
  2. 建構 Fuchsia 映像檔:

    fx build
    
  3. 執行 echo_realm 元件。這會建立用戶端和伺服器元件執行個體,並轉送功能:

    ffx component run /core/ffx-laboratory:echo-client fuchsia-pkg://fuchsia.com/echo-cpp-client#meta/echo_realm.cm
    
  4. 啟動 echo_client 執行個體:

    ffx component start /core/ffx-laboratory:echo_realm/echo_client
    

伺服器元件會在用戶端嘗試連線至 Echo 通訊協定時啟動。您應該會在裝置記錄 (ffx log) 中看到類似以下的輸出內容:

[echo_server][][I] Running C++ echo server with natural types
[echo_server][][I] Incoming connection for fuchsia.examples.Echo
[echo_client][][I] (Natural types) got response: hello
[echo_client][][I] (Natural types) got response: hello
[echo_client][][I] (Natural types) got response: hello
[echo_client][][I] (Natural types) got event: hello
[echo_client][][I] (Wire types) got response: hello
[echo_client][][I] (Natural types) got event: hello
[echo_server][][I] Client disconnected

終止領域元件以停止執行並清除元件執行個體:

ffx component destroy /core/ffx-laboratory:echo_realm

僅連接網域物件

fidl::Client 支援使用自然網域物件線路物件進行呼叫。如果只需要使用線路網域物件,您可以建立 WireClient,藉此公開對等的方法呼叫介面,做為在 fidl::Client 上呼叫 client.wire() 所取得的子集。

WireClient 的建立方式與 Client 相同:

fidl::WireClient client(std::move(*client_end), dispatcher, &event_handler);

fidl::Client 一律會以自然網域物件的形式,向使用者顯示接收的事件。另一方面,fidl::WireClient 會以傳輸網域物件的形式公開接收的事件。為此,傳遞至 WireClient 的事件處理常式必須實作 fidl::WireAsyncEventHandler<Protocol>

  • 實作自然事件處理常式:

    class EventHandler : public fidl::AsyncEventHandler<fuchsia_examples::Echo> {
     public:
      void OnString(fidl::Event<::fuchsia_examples::Echo::OnString>& event) override {
        FX_LOGS(INFO) << "(Natural types) got event: " << event.response();
        loop_.Quit();
      }
    
      // ...
    };
    
  • 實作線路事件處理常式:

    class EventHandler : public fidl::WireAsyncEventHandler<fuchsia_examples::Echo> {
     public:
      void OnString(fidl::WireEvent<fuchsia_examples::Echo::OnString>* event) override {
        FX_LOGS(INFO) << "(Natural types) got event: " << event->response.get();
        loop_.Quit();
      }
    
      // ...
    };
    

同步通話

WireClient 物件也允許同步呼叫,這種呼叫會在收到回應並傳回回應物件之前封鎖。您可以使用 .sync() 存取子來選取這些元件。(例如 client.sync()->EchoString())。

  // Make a synchronous EchoString call, which blocks until it receives the response,
  // then returns a WireResult object for the response.
  fidl::WireResult result_sync = client.sync()->EchoString("hello");
  ZX_ASSERT(result_sync.ok());
  std::string_view response = result_sync->response.get();
  FX_LOGS(INFO) << "Got synchronous response: " << response;

在同步呼叫中,系統會傳回「結果物件」,以同步方式告知呼叫成功或失敗。

使用有線用戶端的完整程式碼範例位於 Fuchsia 結帳頁面://examples/fidl/cpp/client/wire