harmony 鸿蒙交互工具使用指导

  • 2023-06-24
  • 浏览 (610)

交互工具使用指导

概述

Bindgen和CXX工具的主要功能是实现Rust和C/C++之间的交互。其中,Bindgen通过将C接口转换为Rust接口来实现Rust对C的调用,CXX可以通过建立C接口和Rust接口的映射关系来实现C++和Rust的相互调用。

bindgen_and_cxx_tools

Bindgen工具使用指导

操作步骤

下面是一个使用bindgen实现Rust调用C的示例。

  1. 在C代码侧,使用头文件lib.h定义两个接口,接口FuncAAddB用来实现两数求和,接口SayHello用来打印字符串。
   #ifndef BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
   #define BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
   #include <stdint.h>
   #include "build/rust/tests/test_bindgen_test/test_for_hello_world/lib2.h"
   
   uint32_t FuncAAddB(uint32_t a, uint32_t b);
   void SayHello(const char *message);
   
   #endif  //  BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
  1. 在lib.c中添加对两个接口的对应实现。
   #include "build/rust/tests/test_bindgen_test/test_for_hello_world/lib.h"
   #include <stdint.h>
   #include <stdio.h>

   void SayHello(const char *message)
   {
       printf("This is a test for bindgen hello world:\n");
       printf("%s\n", message);
   }

   uint32_t FuncAAddB(uint32_t a, uint32_t b)
   {
       printf("This is a test for bindgen of a + b:\n");
       return a + b;
   }
  1. 添加文件main.rs,就可以在Rust侧通过c_ffi实现对C侧的接口调用。注意Rust侧调用的不安全接口需要使用unsafe封装。
   //!  bindgen test for hello world
   #![allow(clippy::approx_constant)]
   mod c_ffi {
       #![allow(dead_code)]
       #![allow(non_upper_case_globals)]
       #![allow(non_camel_case_types)]
       include!(env!("BINDGEN_RS_FILE"));
   }
   /// pub fn add_two_numbers_in_c
   pub fn add_two_numbers_in_c(a: u32, b: u32) -> u32 {
       unsafe { c_ffi::FuncAAddB(a, b) }
   }

   use std::ffi::c_char;
   use std::ffi::CString;

   /// fn main()
   fn main() {
       println!("{} + {} = {}", 3, 7, add_two_numbers_in_c(3, 7));
       let c_str = CString::new("This is a message from C").unwrap();
       let c_world: *const c_char = c_str.as_ptr() as *const c_char;
       unsafe {
           c_ffi::SayHello(c_world);
       }
   }

  1. 添加构建文件BUILD.gn,建立Rust模块对C模块的依赖。
   import("//build/ohos.gni")

   ohos_shared_library("c_lib") {
     sources = [ "lib.c" ]
     defines = [ "COMPONENT_IMPLEMENTATION" ]
   }

   rust_bindgen("c_lib_bindgen") {
     header = "lib.h"
   }

   ohos_rust_executable("bindgen_test") {
     deps = [ ":c_lib" ]
     deps += [ ":c_lib_bindgen" ]
     sources = [ "main.rs" ]
     bindgen_output = get_target_outputs(":c_lib_bindgen")
     inputs = bindgen_output
     rustenv = [ "BINDGEN_RS_FILE=" + rebase_path(bindgen_output[0]) ]
     crate_root = "main.rs"
   }

调测验证

bindgen_test

CXX工具使用指导

C++调用Rust接口

  1. 在Rust侧文件lib.rs里mod ffi写清楚需要调用的C++接口,并将接口包含在extern “Rust”里面,暴露给C++侧使用。
   //! #[cxx::bridge]
   #[cxx::bridge]
   mod ffi{
       #![allow(dead_code)]
       #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
       struct Shared {
           z: usize,
       }
       extern "Rust"{
           fn print_message_in_rust();
           fn r_return_primitive() -> usize;
           fn r_return_shared() -> Shared;
           fn r_return_rust_string() -> String;
           fn r_return_sum(_: usize, _: usize) -> usize;
       }
   }

   fn print_message_in_rust(){
       println!("Here is a test for cpp call Rust.");
   }
   fn r_return_shared() -> ffi::Shared {
       println!("Here is a message from Rust,test for ffi::Shared:");
       ffi::Shared { z: 1996 }
   }
   fn r_return_primitive() -> usize {
       println!("Here is a message from Rust,test for usize:");
       1997
   }
   fn r_return_rust_string() -> String {
       println!("Here is a message from Rust,test for String");
       "Hello World!".to_owned()
   }
   fn r_return_sum(n1: usize, n2: usize) -> usize {
       println!("Here is a message from Rust,test for {} + {} is:",n1 ,n2);
       n1 + n2
   }

  1. C++侧将cxx工具转换出来的lib.rs.h包含进来,就可以使用C++侧的接口。
   #include <iostream>
   #include "build/rust/tests/test_cxx/src/lib.rs.h"

   int main(int argc, const char* argv[])
   {
       int a = 2021;
       int b = 4;
       print_message_in_rust();
       std::cout << r_return_primitive() << std::endl;
       std::cout << r_return_shared().z << std::endl;
       std::cout << std::string(r_return_rust_string()) << std::endl;
       std::cout << r_return_sum(a, b) << std::endl;
       return 0;
   }
  1. 添加构建文件BUILD.gn。rust_cxx底层调用CXX工具将lib.rs文件转换成lib.rs.h和lib.rs.cc文件,ohos_rust_static_ffi实现Rust侧源码的编译,ohos_executable实现C++侧代码的编译。
   import("//build/ohos.gni")
   import("//build/templates/rust/rust_cxx.gni")

   rust_cxx("test_cxx_exe_gen") {
       sources = [ "src/lib.rs" ]
   }

   ohos_rust_static_ffi("test_cxx_examp_rust") {
       sources = [ "src/lib.rs" ]
       deps = [ "//build/rust:cxx_rustdeps" ]
   }

   ohos_executable("test_cxx_exe") {
       sources = [ "main.cpp" ]
       sources += get_target_outputs(":test_cxx_exe_gen")

       include_dirs = [ "${target_gen_dir}" ]
       deps = [
       ":test_cxx_examp_rust",
       ":test_cxx_exe_gen",
       "//build/rust:cxx_cppdeps",
       ]
   }

调测验证 cpp_call_rust

Rust调用C++

  1. 添加头文件client_blobstore.h。
   #ifndef BUILD_RUST_TESTS_CLIENT_BLOBSTORE_H
   #define BUILD_RUST_TESTS_CLIENT_BLOBSTORE_H
   #include <memory>
   #include "third_party/rust/cxx/include/cxx.h"

   namespace nsp_org {
   namespace nsp_blobstore {
   struct MultiBufs;
   struct Metadata_Blob;

   class client_blobstore {
   public:
       client_blobstore();
       uint64_t put_buf(MultiBufs &buf) const;
       void add_tag(uint64_t blobid, rust::Str add_tag) const;
       Metadata_Blob get_metadata(uint64_t blobid) const;

   private:
       class impl;
       std::shared_ptr<impl> impl;
   };

   std::unique_ptr<client_blobstore> blobstore_client_new();
   } // namespace nsp_blobstore
   } // namespace nsp_org
   #endif
  1. 添加cpp文件client_blobstore.cpp。
   #include <algorithm>
   #include <functional>
   #include <set>
   #include <string>
   #include <unordered_map>
   #include "src/main.rs.h"
   #include "build/rust/tests/test_cxx_rust/include/client_blobstore.h"

   namespace nsp_org {
   namespace nsp_blobstore {
   // Toy implementation of an in-memory nsp_blobstore.
   //
   // In reality the implementation of client_blobstore could be a large complex C++
   // library.
   class client_blobstore::impl {
       friend client_blobstore;
       using Blob = struct {
           std::string data;
           std::set<std::string> tags;
       };
       std::unordered_map<uint64_t, Blob> blobs;
   };

   client_blobstore::client_blobstore() : impl(new class client_blobstore::impl) {}

   // Upload a new blob and return a blobid that serves as a handle to the blob.
   uint64_t client_blobstore::put_buf(MultiBufs &buf) const
   {
       std::string contents;

       // Traverse the caller's res_chunk iterator.
       //
       // In reality there might be sophisticated batching of chunks and/or parallel
       // upload implemented by the nsp_blobstore's C++ client.
       while (true) {
           auto res_chunk = next_chunk(buf);
           if (res_chunk.size() == 0) {
           break;
           }
           contents.append(reinterpret_cast<const char *>(res_chunk.data()), res_chunk.size());
       }

       // Insert into map and provide caller the handle.
       auto res = std::hash<std::string> {} (contents);
       impl->blobs[res] = {std::move(contents), {}};
       return res;
   }

   // Add add_tag to an existing blob.
   void client_blobstore::add_tag(uint64_t blobid, rust::Str add_tag) const
   {
       impl->blobs[blobid].tags.emplace(add_tag);
   }

   // Retrieve get_metadata about a blob.
   Metadata_Blob client_blobstore::get_metadata(uint64_t blobid) const
   {
       Metadata_Blob get_metadata {};
       auto blob = impl->blobs.find(blobid);
       if (blob != impl->blobs.end()) {
           get_metadata.size = blob->second.data.size();
           std::for_each(blob->second.tags.cbegin(), blob->second.tags.cend(),
               [&](auto &t) { get_metadata.tags.emplace_back(t); });
       }
       return get_metadata;
   }

   std::unique_ptr<client_blobstore> blobstore_client_new()
   {
       return std::make_unique<client_blobstore>();
   }
   } // namespace nsp_blobstore
   } // namespace nsp_org

  1. main.rs文件,在main.rs文件的ffi里面,通过宏include!将头文件client_blobstore.h引入进来,从而在Rust的main函数里面就可以通过ffi的方式调用C++的接口。
   //! test_cxx_rust
   #[cxx::bridge(namespace = "nsp_org::nsp_blobstore")]
   mod ffi {
       // Shared structs with fields visible to both languages.
       struct Metadata_Blob {
           size: usize,
           tags: Vec<String>,
       }

       // Rust types and signatures exposed to C++.
       extern "Rust" {
           type MultiBufs;

           fn next_chunk(buf: &mut MultiBufs) -> &[u8];
       }

       // C++ types and signatures exposed to Rust.
       unsafe extern "C++" {
           include!("build/rust/tests/test_cxx_rust/include/client_blobstore.h");

           type client_blobstore;

           fn blobstore_client_new() -> UniquePtr<client_blobstore>;
           fn put_buf(&self, parts: &mut MultiBufs) -> u64;
           fn add_tag(&self, blobid: u64, add_tag: &str);
           fn get_metadata(&self, blobid: u64) -> Metadata_Blob;
       }
   }

   // An iterator over contiguous chunks of a discontiguous file object.
   //
   // Toy implementation uses a Vec<Vec<u8>> but in reality this might be iterating
   // over some more complex Rust data structure like a rope, or maybe loading
   // chunks lazily from somewhere.
   /// pub struct MultiBufs
   pub struct MultiBufs {
       chunks: Vec<Vec<u8>>,
       pos: usize,
   }
   /// pub fn next_chunk
   pub fn next_chunk(buf: &mut MultiBufs) -> &[u8] {
       let next = buf.chunks.get(buf.pos);
       buf.pos += 1;
       next.map_or(&[], Vec::as_slice)
   }

   /// fn main()
   fn main() {
       let client = ffi::blobstore_client_new();

       // Upload a blob.
       let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()];
       let mut buf = MultiBufs { chunks, pos: 0 };
       let blobid = client.put_buf(&mut buf);
       println!("This is a test for Rust call cpp:");
       println!("blobid = {}", blobid);

       // Add a add_tag.
       client.add_tag(blobid, "rust");

       // Read back the tags.
       let get_metadata = client.get_metadata(blobid);
       println!("tags = {:?}", get_metadata.tags);
   }
  1. 添加构建文件BUILD.gn。使用CXX将main.rs转换成lib.rs.h和lib.rs.cc,同时将产物作为test_cxx_rust_staticlib的源码,编译Rust源码main.rs并将test_cxx_rust_staticlib依赖进来。
   import("//build/ohos.gni")

   rust_cxx("test_cxx_rust_gen") {
     sources = [ "src/main.rs" ]
   }

   ohos_static_library("test_cxx_rust_staticlib") {
     sources = [ "src/client_blobstore.cpp" ]
     sources += get_target_outputs(":test_cxx_rust_gen")
     include_dirs = [
       "${target_gen_dir}",
       "//third_party/rust/cxx/v1/crate/include",
       "include",
     ]
     deps = [
       ":test_cxx_rust_gen",
       "//build/rust:cxx_cppdeps",
     ]
   }

   ohos_rust_executable("test_cxx_rust") {
     sources = [ "src/main.rs" ]
     deps = [
       ":test_cxx_rust_staticlib",
       "//build/rust:cxx_rustdeps",
     ]
   }

调测验证 rust_call_cpp

你可能感兴趣的鸿蒙文章

harmony 鸿蒙子系统开发

harmony 鸿蒙AI框架开发指导

harmony 鸿蒙NNRt接入适配指导

harmony 鸿蒙应用特权配置指南

harmony 鸿蒙开发实例

harmony 鸿蒙搭建环境

harmony 鸿蒙开发指导

harmony 鸿蒙概述

harmony 鸿蒙ArkCompiler开发指导

harmony 鸿蒙窗口标题栏定制开发指导

0  赞