• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.hlib.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.h119
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.cpp150
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.hlib.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 &lt;=&gt; 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>&amp;str</td><td>rust::Str</td><td></td></tr>
395<tr><td>&amp;[T]</td><td>rust::Slice&lt;const T&gt;</td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr>
396<tr><td>&amp;mut [T]</td><td>rust::Slice&lt;T&gt;</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&lt;T&gt;</td><td>rust::Box&lt;T&gt;</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&lt;T&gt;</a></td><td>std::unique_ptr&lt;T&gt;</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&lt;T&gt;</a></td><td>std::shared_ptr&lt;T&gt;</td><td><sup><i>cannot hold opaque Rust type</i></sup></td></tr>
401<tr><td>[T; N]</td><td>std::array&lt;T, N&gt;</td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr>
402<tr><td>Vec&lt;T&gt;</td><td>rust::Vec&lt;T&gt;</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&lt;T&gt;</a></td><td>std::vector&lt;T&gt;</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) -&gt; V</td><td>rust::Fn&lt;V(T, U)&gt;</td><td><sup><i>only passing from Rust to C++ is implemented so far</i></sup></td></tr>
406<tr><td>Result&lt;T&gt;</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&lt;K, V&gt;</td><td><sup><i>tbd</i></sup></td></tr>
416<tr><td>HashMap&lt;K, V&gt;</td><td><sup><i>tbd</i></sup></td></tr>
417<tr><td>Arc&lt;T&gt;</td><td><sup><i>tbd</i></sup></td></tr>
418<tr><td>Option&lt;T&gt;</td><td><sup><i>tbd</i></sup></td></tr>
419<tr><td><sup><i>tbd</i></sup></td><td>std::map&lt;K, V&gt;</td></tr>
420<tr><td><sup><i>tbd</i></sup></td><td>std::unordered_map&lt;K, V&gt;</td></tr>
421</table>
422
423<br>
424
425## 开发者贡献
426
427当前CXX工具还没有达到普遍使用阶段,在使用该工具的过程中有任何问题欢迎开发者在社区issue中反馈。
428
429<br>
430