6 Star 2 Fork 12

OpenHarmony / third_party_rust_cxx

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

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

github crates.io docs.rs build status

引入背景

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

CXX通过FFI(Foreign 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或Buck,CXX提供了另一种方式产生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 Rust name in C++ restrictions
String rust::String
&str rust::Str
&[T] rust::Slice<const T> cannot hold opaque C++ type
&mut [T] rust::Slice<T> cannot hold opaque C++ type
CXXString std::string cannot 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 T T*, const T* fn with a raw pointer argument must be declared unsafe to call
fn(T, U) -> V rust::Fn<V(T, U)> only passing from Rust to C++ is implemented so far
Result<T> throw/catch allowed as return type only

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

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

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

开发者贡献

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


Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

"提供从 Rust 中安全、简单地调用 C++ 代码的方法。 | A Rust library that provides a safe and easy way to call C++ code from Rust." 展开 收起
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
1
https://gitee.com/openharmony/third_party_rust_cxx.git
git@gitee.com:openharmony/third_party_rust_cxx.git
openharmony
third_party_rust_cxx
third_party_rust_cxx
master

搜索帮助