1CXX — Rust和C++之间的安全FFI 2========================================= 3 4[<img alt="github" src="https://img.shields.io/badge/github-dtolnay/CXX-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/dtolnay/CXX) 5[<img alt="crates.io" src="https://img.shields.io/crates/v/CXX.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/CXX) 6[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-CXX-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/cxx) 7[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/dtolnay/CXX/ci.yml" height="20">](https://github.com/dtolnay/CXX) 8 9 10## 引入背景 11 12 13CXX工具提供了一种安全的互相调用机制,可以实现rust和C++的互相调用。 14 15CXX通过FFI(Foreign Function Interface)和函数签名的形式来实现接口和类型声明,并对类型和函数签名进行静态分析,以维护Rust和C++的不变量和要求。 16 17<br> 18 19## CXX工具在OH上的使用指导 20 21### C++调用Rust接口 22 231. 在Rust侧文件lib.rs里mod ffi写清楚需要调用的C++接口,并将接口包含在extern "Rust"里面,暴露给C++侧使用。 24 25 ```rust 26 //! #[cxx::bridge] 27 #[cxx::bridge] 28 mod ffi{ 29 #![allow(dead_code)] 30 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] 31 struct Shared { 32 z: usize, 33 } 34 extern "Rust"{ 35 fn print_message_in_rust(); 36 fn r_return_primitive() -> usize; 37 fn r_return_shared() -> Shared; 38 fn r_return_rust_string() -> String; 39 fn r_return_sum(_: usize, _: usize) -> usize; 40 } 41 } 42 43 fn print_message_in_rust(){ 44 println!("Here is a test for cpp call Rust."); 45 } 46 fn r_return_shared() -> ffi::Shared { 47 println!("Here is a message from Rust,test for ffi::Shared:"); 48 ffi::Shared { z: 1996 } 49 } 50 fn r_return_primitive() -> usize { 51 println!("Here is a message from Rust,test for usize:"); 52 1997 53 } 54 fn r_return_rust_string() -> String { 55 println!("Here is a message from Rust,test for String"); 56 "Hello World!".to_owned() 57 } 58 fn r_return_sum(n1: usize, n2: usize) -> usize { 59 println!("Here is a message from Rust,test for {} + {} is:",n1 ,n2); 60 n1 + n2 61 } 62 63 ``` 64 652. C++侧将cxx工具转换出来的lib.rs.h包含进来,就可以使用C++侧的接口。 66 67 ```c++ 68 #include <iostream> 69 #include "build/rust/tests/test_cxx/src/lib.rs.h" 70 71 int main(int argc, const char* argv[]) 72 { 73 int a = 2021; 74 int b = 4; 75 print_message_in_rust(); 76 std::cout << r_return_primitive() << std::endl; 77 std::cout << r_return_shared().z << std::endl; 78 std::cout << std::string(r_return_rust_string()) << std::endl; 79 std::cout << r_return_sum(a, b) << std::endl; 80 return 0; 81 } 82 ``` 83 843. 添加构建文件BUILD.gn。rust_cxx底层调用CXX工具将lib.rs文件转换成lib.rs.h和lib.rs.cc文件,ohos_rust_static_ffi实现Rust侧源码的编译,ohos_executable实现C++侧代码的编译。 85 86 ``` 87 import("//build/ohos.gni") 88 import("//build/templates/rust/rust_cxx.gni") 89 90 rust_cxx("test_cxx_exe_gen") { 91 sources = [ "src/lib.rs" ] 92 } 93 94 ohos_rust_static_ffi("test_cxx_examp_rust") { 95 sources = [ "src/lib.rs" ] 96 deps = [ "//build/rust:cxx_rustdeps" ] 97 } 98 99 ohos_executable("test_cxx_exe") { 100 sources = [ "main.cpp" ] 101 sources += get_target_outputs(":test_cxx_exe_gen") 102 103 include_dirs = [ "${target_gen_dir}" ] 104 deps = [ 105 ":test_cxx_examp_rust", 106 ":test_cxx_exe_gen", 107 "//build/rust:cxx_cppdeps", 108 ] 109 } 110 ``` 111 112**调测验证** 113![cpp_call_rust](./cpp_call_rust.png) 114 115 116### Rust调用C++ 117 1181. 添加头文件client_blobstore.h。 119 120 ```c++ 121 #ifndef BUILD_RUST_TESTS_CLIENT_BLOBSTORE_H 122 #define BUILD_RUST_TESTS_CLIENT_BLOBSTORE_H 123 #include <memory> 124 #include "third_party/rust/cxx/include/cxx.h" 125 126 namespace nsp_org { 127 namespace nsp_blobstore { 128 struct MultiBufs; 129 struct Metadata_Blob; 130 131 class client_blobstore { 132 public: 133 client_blobstore(); 134 uint64_t put_buf(MultiBufs &buf) const; 135 void add_tag(uint64_t blobid, rust::Str add_tag) const; 136 Metadata_Blob get_metadata(uint64_t blobid) const; 137 138 private: 139 class impl; 140 std::shared_ptr<impl> impl; 141 }; 142 143 std::unique_ptr<client_blobstore> blobstore_client_new(); 144 } // namespace nsp_blobstore 145 } // namespace nsp_org 146 #endif 147 ``` 148 1492. 添加cpp文件client_blobstore.cpp。 150 151 ```c++ 152 #include <algorithm> 153 #include <functional> 154 #include <set> 155 #include <string> 156 #include <unordered_map> 157 #include "src/main.rs.h" 158 #include "build/rust/tests/test_cxx_rust/include/client_blobstore.h" 159 160 namespace nsp_org { 161 namespace nsp_blobstore { 162 // Toy implementation of an in-memory nsp_blobstore. 163 // 164 // In reality the implementation of client_blobstore could be a large complex C++ 165 // library. 166 class client_blobstore::impl { 167 friend client_blobstore; 168 using Blob = struct { 169 std::string data; 170 std::set<std::string> tags; 171 }; 172 std::unordered_map<uint64_t, Blob> blobs; 173 }; 174 175 client_blobstore::client_blobstore() : impl(new class client_blobstore::impl) {} 176 177 // Upload a new blob and return a blobid that serves as a handle to the blob. 178 uint64_t client_blobstore::put_buf(MultiBufs &buf) const 179 { 180 std::string contents; 181 182 // Traverse the caller's res_chunk iterator. 183 // 184 // In reality there might be sophisticated batching of chunks and/or parallel 185 // upload implemented by the nsp_blobstore's C++ client. 186 while (true) { 187 auto res_chunk = next_chunk(buf); 188 if (res_chunk.size() == 0) { 189 break; 190 } 191 contents.append(reinterpret_cast<const char *>(res_chunk.data()), res_chunk.size()); 192 } 193 194 // Insert into map and provide caller the handle. 195 auto res = std::hash<std::string> {} (contents); 196 impl->blobs[res] = {std::move(contents), {}}; 197 return res; 198 } 199 200 // Add add_tag to an existing blob. 201 void client_blobstore::add_tag(uint64_t blobid, rust::Str add_tag) const 202 { 203 impl->blobs[blobid].tags.emplace(add_tag); 204 } 205 206 // Retrieve get_metadata about a blob. 207 Metadata_Blob client_blobstore::get_metadata(uint64_t blobid) const 208 { 209 Metadata_Blob get_metadata {}; 210 auto blob = impl->blobs.find(blobid); 211 if (blob != impl->blobs.end()) { 212 get_metadata.size = blob->second.data.size(); 213 std::for_each(blob->second.tags.cbegin(), blob->second.tags.cend(), 214 [&](auto &t) { get_metadata.tags.emplace_back(t); }); 215 } 216 return get_metadata; 217 } 218 219 std::unique_ptr<client_blobstore> blobstore_client_new() 220 { 221 return std::make_unique<client_blobstore>(); 222 } 223 } // namespace nsp_blobstore 224 } // namespace nsp_org 225 226 ``` 227 2283. main.rs文件,在main.rs文件的ffi里面,通过宏include!将头文件client_blobstore.h引入进来,从而在Rust的main函数里面就可以通过ffi的方式调用C++的接口。 229 230 ```rust 231 //! test_cxx_rust 232 #[cxx::bridge(namespace = "nsp_org::nsp_blobstore")] 233 mod ffi { 234 // Shared structs with fields visible to both languages. 235 struct Metadata_Blob { 236 size: usize, 237 tags: Vec<String>, 238 } 239 240 // Rust types and signatures exposed to C++. 241 extern "Rust" { 242 type MultiBufs; 243 244 fn next_chunk(buf: &mut MultiBufs) -> &[u8]; 245 } 246 247 // C++ types and signatures exposed to Rust. 248 unsafe extern "C++" { 249 include!("build/rust/tests/test_cxx_rust/include/client_blobstore.h"); 250 251 type client_blobstore; 252 253 fn blobstore_client_new() -> UniquePtr<client_blobstore>; 254 fn put_buf(&self, parts: &mut MultiBufs) -> u64; 255 fn add_tag(&self, blobid: u64, add_tag: &str); 256 fn get_metadata(&self, blobid: u64) -> Metadata_Blob; 257 } 258 } 259 260 // An iterator over contiguous chunks of a discontiguous file object. 261 // 262 // Toy implementation uses a Vec<Vec<u8>> but in reality this might be iterating 263 // over some more complex Rust data structure like a rope, or maybe loading 264 // chunks lazily from somewhere. 265 /// pub struct MultiBufs 266 pub struct MultiBufs { 267 chunks: Vec<Vec<u8>>, 268 pos: usize, 269 } 270 /// pub fn next_chunk 271 pub fn next_chunk(buf: &mut MultiBufs) -> &[u8] { 272 let next = buf.chunks.get(buf.pos); 273 buf.pos += 1; 274 next.map_or(&[], Vec::as_slice) 275 } 276 277 /// fn main() 278 fn main() { 279 let client = ffi::blobstore_client_new(); 280 281 // Upload a blob. 282 let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()]; 283 let mut buf = MultiBufs { chunks, pos: 0 }; 284 let blobid = client.put_buf(&mut buf); 285 println!("This is a test for Rust call cpp:"); 286 println!("blobid = {}", blobid); 287 288 // Add a add_tag. 289 client.add_tag(blobid, "rust"); 290 291 // Read back the tags. 292 let get_metadata = client.get_metadata(blobid); 293 println!("tags = {:?}", get_metadata.tags); 294 } 295 ``` 296 2974. 添加构建文件BUILD.gn。使用CXX将main.rs转换成lib.rs.h和lib.rs.cc,同时将产物作为test_cxx_rust_staticlib的源码,编译Rust源码main.rs并将test_cxx_rust_staticlib依赖进来。 298 299 ``` 300 import("//build/ohos.gni") 301 302 rust_cxx("test_cxx_rust_gen") { 303 sources = [ "src/main.rs" ] 304 } 305 306 ohos_static_library("test_cxx_rust_staticlib") { 307 sources = [ "src/client_blobstore.cpp" ] 308 sources += get_target_outputs(":test_cxx_rust_gen") 309 include_dirs = [ 310 "${target_gen_dir}", 311 "//third_party/rust/cxx/v1/crate/include", 312 "include", 313 ] 314 deps = [ 315 ":test_cxx_rust_gen", 316 "//build/rust:cxx_cppdeps", 317 ] 318 } 319 320 ohos_rust_executable("test_cxx_rust") { 321 sources = [ "src/main.rs" ] 322 deps = [ 323 ":test_cxx_rust_staticlib", 324 "//build/rust:cxx_rustdeps", 325 ] 326 } 327 ``` 328 329**调测验证** 330![rust_call_cpp](./rust_call_cpp.png) 331 332<br> 333 334 335 336## 与bindgen的对比 337 338bindgen主要用来实现rust代码对c接口的单向调用;CXX工具可以实现rust和C++的互相调用。 339 340<br> 341 342## 基于cargo的构建 343 344对于由Cargo的构建,需要使用一个构建脚本来运行CXX的C++代码生成器。 345 346典型的构建脚本如下: 347 348[`cc::Build`]: https://docs.rs/cc/1.0/cc/struct.Build.html 349 350```toml 351# Cargo.toml 352 353[build-dependencies] 354CXX-build = "1.0" 355``` 356 357```rust 358// build.rs 359 360fn main() { 361 CXX_build::bridge("src/main.rs") // returns a cc::Build 362 .file("src/demo.cc") 363 .flag_if_supported("-std=C++11") 364 .compile("cxxbridge-demo"); 365 366 println!("cargo:rerun-if-changed=src/main.rs"); 367 println!("cargo:rerun-if-changed=src/demo.cc"); 368 println!("cargo:rerun-if-changed=include/demo.h"); 369} 370``` 371 372<br> 373 374## 基于非cargo的构建 375 376对于在非Cargo构建中的使用,如Bazel或Buck,CXX提供了另一种方式产生C++侧的头文件和源代码文件,作为一个独立的命令行工具使用。 377 378```bash 379$ cargo install cxxbridge-cmd 380$ cxxbridge src/main.rs --header > path/to/mybridge.h 381$ cxxbridge src/main.rs > path/to/mybridge.cc 382``` 383 384<br> 385 386 387## 内置类型 388 389除了所有的原生类型(i32 <=> int32_t)之外,还有以下常见类型可用于共享结构的字段以及函数的参数和返回值。 390 391<table> 392<tr><th>name in Rust</th><th>name in C++</th><th>restrictions</th></tr> 393<tr><td>String</td><td>rust::String</td><td></td></tr> 394<tr><td>&str</td><td>rust::Str</td><td></td></tr> 395<tr><td>&[T]</td><td>rust::Slice<const T></td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr> 396<tr><td>&mut [T]</td><td>rust::Slice<T></td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr> 397<tr><td><a href="https://docs.rs/cxx/1.0/CXX/struct.CXXString.html">CXXString</a></td><td>std::string</td><td><sup><i>cannot be passed by value</i></sup></td></tr> 398<tr><td>Box<T></td><td>rust::Box<T></td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr> 399<tr><td><a href="https://docs.rs/cxx/1.0/CXX/struct.UniquePtr.html">UniquePtr<T></a></td><td>std::unique_ptr<T></td><td><sup><i>cannot hold opaque Rust type</i></sup></td></tr> 400<tr><td><a href="https://docs.rs/cxx/1.0/CXX/struct.SharedPtr.html">SharedPtr<T></a></td><td>std::shared_ptr<T></td><td><sup><i>cannot hold opaque Rust type</i></sup></td></tr> 401<tr><td>[T; N]</td><td>std::array<T, N></td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr> 402<tr><td>Vec<T></td><td>rust::Vec<T></td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr> 403<tr><td><a href="https://docs.rs/cxx/1.0/CXX/struct.CXXVector.html">CXXVector<T></a></td><td>std::vector<T></td><td><sup><i>cannot be passed by value, cannot hold opaque Rust type</i></sup></td></tr> 404<tr><td>*mut T, *const T</td><td>T*, const T*</td><td><sup><i>fn with a raw pointer argument must be declared unsafe to call</i></sup></td></tr> 405<tr><td>fn(T, U) -> V</td><td>rust::Fn<V(T, U)></td><td><sup><i>only passing from Rust to C++ is implemented so far</i></sup></td></tr> 406<tr><td>Result<T></td><td>throw/catch</td><td><sup><i>allowed as return type only</i></sup></td></tr> 407</table> 408 409`rust`命名空间的C++ API是由*include/CXX.h*文件定义的。使用这些类型时种类的时候,需要C++代码中包含这个头文件。 410 411以下类型很快被支持,只是还没有实现。 412 413<table> 414<tr><th>name in Rust</th><th>name in C++</th></tr> 415<tr><td>BTreeMap<K, V></td><td><sup><i>tbd</i></sup></td></tr> 416<tr><td>HashMap<K, V></td><td><sup><i>tbd</i></sup></td></tr> 417<tr><td>Arc<T></td><td><sup><i>tbd</i></sup></td></tr> 418<tr><td>Option<T></td><td><sup><i>tbd</i></sup></td></tr> 419<tr><td><sup><i>tbd</i></sup></td><td>std::map<K, V></td></tr> 420<tr><td><sup><i>tbd</i></sup></td><td>std::unordered_map<K, V></td></tr> 421</table> 422 423<br> 424 425## 开发者贡献 426 427当前CXX工具还没有达到普遍使用阶段,在使用该工具的过程中有任何问题欢迎开发者在社区issue中反馈。 428 429<br> 430