third_party_rust_cxx/README_zh.md
fangting 9bb96f9ac3 add oh user guide
Signed-off-by: fangting <fangting12@huawei.com>
2023-05-04 11:40:57 +08:00

14 KiB
Raw Permalink Blame History

CXX — Rust和C++之间的安全FFI

github crates.io docs.rs build status

引入背景

CXX工具提供了一种安全的互相调用机制可以实现rust和C++的互相调用。

CXX通过FFIForeign Function Interface和函数签名的形式来实现接口和类型声明并对类型和函数签名进行静态分析以维护Rust和C++的不变量和要求。


CXX工具在OH上的使用指导

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
    }
    
    
  2. 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;
    }
    
  3. 添加构建文件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
    
  2. 添加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
    
    
  3. 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);
    }
    
  4. 添加构建文件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


与bindgen的对比

bindgen主要用来实现rust代码对c接口的单向调用CXX工具可以实现rust和C++的互相调用。


基于cargo的构建

对于由Cargo的构建需要使用一个构建脚本来运行CXX的C++代码生成器。

典型的构建脚本如下:

# Cargo.toml

[build-dependencies]
CXX-build = "1.0"
// build.rs

fn main() {
    CXX_build::bridge("src/main.rs")  // returns a cc::Build
        .file("src/demo.cc")
        .flag_if_supported("-std=C++11")
        .compile("cxxbridge-demo");

    println!("cargo:rerun-if-changed=src/main.rs");
    println!("cargo:rerun-if-changed=src/demo.cc");
    println!("cargo:rerun-if-changed=include/demo.h");
}

基于非cargo的构建

对于在非Cargo构建中的使用如Bazel或BuckCXX提供了另一种方式产生C++侧的头文件和源代码文件,作为一个独立的命令行工具使用。

$ cargo install cxxbridge-cmd
$ cxxbridge src/main.rs --header > path/to/mybridge.h
$ cxxbridge src/main.rs > path/to/mybridge.cc

内置类型

除了所有的原生类型i32 <=> int32_t之外还有以下常见类型可用于共享结构的字段以及函数的参数和返回值。

name in Rustname in C++restrictions
Stringrust::String
&strrust::Str
&[T]rust::Slice<const T>cannot hold opaque C++ type
&mut [T]rust::Slice<T>cannot hold opaque C++ type
CXXStringstd::stringcannot be passed by value
Box<T>rust::Box<T>cannot hold opaque C++ type
UniquePtr<T>std::unique_ptr<T>cannot hold opaque Rust type
SharedPtr<T>std::shared_ptr<T>cannot hold opaque Rust type
[T; N]std::array<T, N>cannot hold opaque C++ type
Vec<T>rust::Vec<T>cannot hold opaque C++ type
CXXVector<T>std::vector<T>cannot be passed by value, cannot hold opaque Rust type
*mut T, *const TT*, const T*fn with a raw pointer argument must be declared unsafe to call
fn(T, U) -> Vrust::Fn<V(T, U)>only passing from Rust to C++ is implemented so far
Result<T>throw/catchallowed as return type only

rust命名空间的C++ API是由include/CXX.h文件定义的。使用这些类型时种类的时候需要C++代码中包含这个头文件。

以下类型很快被支持,只是还没有实现。

name in Rustname in C++
BTreeMap<K, V>tbd
HashMap<K, V>tbd
Arc<T>tbd
Option<T>tbd
tbdstd::map<K, V>
tbdstd::unordered_map<K, V>

开发者贡献

当前CXX工具还没有达到普遍使用阶段在使用该工具的过程中有任何问题欢迎开发者在社区issue中反馈。