1# 交互工具使用指导
2
3## 概述
4
5Bindgen和CXX工具的主要功能是实现Rust和C/C++之间的交互。其中,Bindgen通过将C接口转换为Rust接口来实现Rust对C的调用,CXX可以通过建立C接口和Rust接口的映射关系来实现C++和Rust的相互调用。
6
7![bindgen_and_cxx_tools](./figures/bindgen_and_cxx_tools.png)
8
9## Bindgen工具使用指导
10
11### 操作步骤
12下面是一个使用bindgen实现Rust调用C的示例。
13
141. 在C代码侧,使用头文件lib.h定义两个接口,接口FuncAAddB用来实现两数求和,接口SayHello用来打印字符串。
15
16   ```c
17   #ifndef BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
18   #define BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
19   #include <stdint.h>
20   #include "build/rust/tests/test_bindgen_test/test_for_hello_world/lib2.h"
21
22   uint32_t FuncAAddB(uint32_t a, uint32_t b);
23   void SayHello(const char *message);
24
25   #endif  //  BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
26   ```
27
28
292. 在lib.c中添加对两个接口的对应实现。
30
31   ```c
32   #include "build/rust/tests/test_bindgen_test/test_for_hello_world/lib.h"
33   #include <stdint.h>
34   #include <stdio.h>
35
36   void SayHello(const char *message)
37   {
38       printf("This is a test for bindgen hello world:\n");
39       printf("%s\n", message);
40   }
41
42   uint32_t FuncAAddB(uint32_t a, uint32_t b)
43   {
44       printf("This is a test for bindgen of a + b:\n");
45       return a + b;
46   }
47   ```
48
493. 添加文件main.rs,就可以在Rust侧通过c_ffi实现对C侧的接口调用。注意Rust侧调用的不安全接口需要使用unsafe封装。
50
51   ```rust
52   //!  bindgen test for hello world
53   #![allow(clippy::approx_constant)]
54   mod c_ffi {
55       #![allow(dead_code)]
56       #![allow(non_upper_case_globals)]
57       #![allow(non_camel_case_types)]
58       include!(env!("BINDGEN_RS_FILE"));
59   }
60   /// pub fn add_two_numbers_in_c
61   pub fn add_two_numbers_in_c(a: u32, b: u32) -> u32 {
62       unsafe { c_ffi::FuncAAddB(a, b) }
63   }
64
65   use std::ffi::c_char;
66   use std::ffi::CString;
67
68   /// fn main()
69   fn main() {
70       println!("{} + {} = {}", 3, 7, add_two_numbers_in_c(3, 7));
71       let c_str = CString::new("This is a message from C").unwrap();
72       let c_world: *const c_char = c_str.as_ptr() as *const c_char;
73       unsafe {
74           c_ffi::SayHello(c_world);
75       }
76   }
77
78   ```
79
804. 添加构建文件BUILD.gn,建立Rust模块对C模块的依赖。
81
82   ```GN
83   import("//build/ohos.gni")
84
85   ohos_shared_library("c_lib") {
86     sources = [ "lib.c" ]
87     defines = [ "COMPONENT_IMPLEMENTATION" ]
88   }
89
90   rust_bindgen("c_lib_bindgen") {
91     header = "lib.h"
92   }
93
94   ohos_rust_executable("bindgen_test") {
95     deps = [ ":c_lib" ]
96     deps += [ ":c_lib_bindgen" ]
97     sources = [ "main.rs" ]
98     bindgen_output = get_target_outputs(":c_lib_bindgen")
99     inputs = bindgen_output
100     rustenv = [ "BINDGEN_RS_FILE=" + rebase_path(bindgen_output[0]) ]
101     crate_root = "main.rs"
102   }
103   ```
104
105**调测验证**
106
107![bindgen_test](./figures/bindgen_test.png)
108
109
110## CXX工具使用指导
111
112### C++调用Rust接口
113
1141. 在Rust侧文件lib.rs里mod ffi写清楚需要调用的C++接口,并将接口包含在extern "Rust"里面,暴露给C++侧使用。
115
116   ```rust
117   //! #[cxx::bridge]
118   #[cxx::bridge]
119   mod ffi{
120       #![allow(dead_code)]
121       #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
122       struct Shared {
123           z: usize,
124       }
125       extern "Rust"{
126           fn print_message_in_rust();
127           fn r_return_primitive() -> usize;
128           fn r_return_shared() -> Shared;
129           fn r_return_rust_string() -> String;
130           fn r_return_sum(_: usize, _: usize) -> usize;
131       }
132   }
133
134   fn print_message_in_rust(){
135       println!("Here is a test for cpp call Rust.");
136   }
137   fn r_return_shared() -> ffi::Shared {
138       println!("Here is a message from Rust,test for ffi::Shared:");
139       ffi::Shared { z: 1996 }
140   }
141   fn r_return_primitive() -> usize {
142       println!("Here is a message from Rust,test for usize:");
143       1997
144   }
145   fn r_return_rust_string() -> String {
146       println!("Here is a message from Rust,test for String");
147       "Hello World!".to_owned()
148   }
149   fn r_return_sum(n1: usize, n2: usize) -> usize {
150       println!("Here is a message from Rust,test for {} + {} is:",n1 ,n2);
151       n1 + n2
152   }
153
154   ```
155
1562. C++侧将cxx工具转换出来的lib.rs.h包含进来,就可以使用C++侧的接口。
157
158   ```c++
159   #include <iostream>
160   #include "build/rust/tests/test_cxx/src/lib.rs.h"
161
162   int main(int argc, const char* argv[])
163   {
164       int a = 2021;
165       int b = 4;
166       print_message_in_rust();
167       std::cout << r_return_primitive() << std::endl;
168       std::cout << r_return_shared().z << std::endl;
169       std::cout << std::string(r_return_rust_string()) << std::endl;
170       std::cout << r_return_sum(a, b) << std::endl;
171       return 0;
172   }
173   ```
174
1753. 添加构建文件BUILD.gn。rust_cxx底层调用CXX工具将lib.rs文件转换成lib.rs.hlib.rs.cc文件,ohos_rust_static_ffi实现Rust侧源码的编译,ohos_executable实现C++侧代码的编译。
176
177   ```
178   import("//build/ohos.gni")
179   import("//build/templates/rust/rust_cxx.gni")
180
181   rust_cxx("test_cxx_exe_gen") {
182       sources = [ "src/lib.rs" ]
183   }
184
185   ohos_rust_static_ffi("test_cxx_examp_rust") {
186       sources = [ "src/lib.rs" ]
187       deps = [ "//build/rust:cxx_rustdeps" ]
188   }
189
190   ohos_executable("test_cxx_exe") {
191       sources = [ "main.cpp" ]
192       sources += get_target_outputs(":test_cxx_exe_gen")
193
194       include_dirs = [ "${target_gen_dir}" ]
195       deps = [
196       ":test_cxx_examp_rust",
197       ":test_cxx_exe_gen",
198       "//build/rust:cxx_cppdeps",
199       ]
200   }
201   ```
202
203**调测验证**
204![cpp_call_rust](./figures/cpp_call_rust.png)
205
206
207### Rust调用C++
208
2091. 添加头文件client_blobstore.h210
211   ```c++
212   #ifndef BUILD_RUST_TESTS_CLIENT_BLOBSTORE_H
213   #define BUILD_RUST_TESTS_CLIENT_BLOBSTORE_H
214   #include <memory>
215   #include "third_party/rust/cxx/include/cxx.h"
216
217   namespace nsp_org {
218   namespace nsp_blobstore {
219   struct MultiBufs;
220   struct Metadata_Blob;
221
222   class client_blobstore {
223   public:
224       client_blobstore();
225       uint64_t put_buf(MultiBufs &buf) const;
226       void add_tag(uint64_t blobid, rust::Str add_tag) const;
227       Metadata_Blob get_metadata(uint64_t blobid) const;
228
229   private:
230       class impl;
231       std::shared_ptr<impl> impl;
232   };
233
234   std::unique_ptr<client_blobstore> blobstore_client_new();
235   } // namespace nsp_blobstore
236   } // namespace nsp_org
237   #endif
238   ```
239
2402. 添加cpp文件client_blobstore.cpp241
242   ```c++
243   #include <algorithm>
244   #include <functional>
245   #include <set>
246   #include <string>
247   #include <unordered_map>
248   #include "src/main.rs.h"
249   #include "build/rust/tests/test_cxx_rust/include/client_blobstore.h"
250
251   namespace nsp_org {
252   namespace nsp_blobstore {
253   // Toy implementation of an in-memory nsp_blobstore.
254   //
255   // In reality the implementation of client_blobstore could be a large complex C++
256   // library.
257   class client_blobstore::impl {
258       friend client_blobstore;
259       using Blob = struct {
260           std::string data;
261           std::set<std::string> tags;
262       };
263       std::unordered_map<uint64_t, Blob> blobs;
264   };
265
266   client_blobstore::client_blobstore() : impl(new class client_blobstore::impl) {}
267
268   // Upload a new blob and return a blobid that serves as a handle to the blob.
269   uint64_t client_blobstore::put_buf(MultiBufs &buf) const
270   {
271       std::string contents;
272
273       // Traverse the caller's res_chunk iterator.
274       //
275       // In reality there might be sophisticated batching of chunks and/or parallel
276       // upload implemented by the nsp_blobstore's C++ client.
277       while (true) {
278           auto res_chunk = next_chunk(buf);
279           if (res_chunk.size() == 0) {
280           break;
281           }
282           contents.append(reinterpret_cast<const char *>(res_chunk.data()), res_chunk.size());
283       }
284
285       // Insert into map and provide caller the handle.
286       auto res = std::hash<std::string> {} (contents);
287       impl->blobs[res] = {std::move(contents), {}};
288       return res;
289   }
290
291   // Add add_tag to an existing blob.
292   void client_blobstore::add_tag(uint64_t blobid, rust::Str add_tag) const
293   {
294       impl->blobs[blobid].tags.emplace(add_tag);
295   }
296
297   // Retrieve get_metadata about a blob.
298   Metadata_Blob client_blobstore::get_metadata(uint64_t blobid) const
299   {
300       Metadata_Blob get_metadata {};
301       auto blob = impl->blobs.find(blobid);
302       if (blob != impl->blobs.end()) {
303           get_metadata.size = blob->second.data.size();
304           std::for_each(blob->second.tags.cbegin(), blob->second.tags.cend(),
305               [&](auto &t) { get_metadata.tags.emplace_back(t); });
306       }
307       return get_metadata;
308   }
309
310   std::unique_ptr<client_blobstore> blobstore_client_new()
311   {
312       return std::make_unique<client_blobstore>();
313   }
314   } // namespace nsp_blobstore
315   } // namespace nsp_org
316
317   ```
318
3193. main.rs文件,在main.rs文件的ffi里面,通过宏include!将头文件client_blobstore.h引入进来,从而在Rust的main函数里面就可以通过ffi的方式调用C++的接口。
320
321   ```rust
322   //! test_cxx_rust
323   #[cxx::bridge(namespace = "nsp_org::nsp_blobstore")]
324   mod ffi {
325       // Shared structs with fields visible to both languages.
326       struct Metadata_Blob {
327           size: usize,
328           tags: Vec<String>,
329       }
330
331       // Rust types and signatures exposed to C++.
332       extern "Rust" {
333           type MultiBufs;
334
335           fn next_chunk(buf: &mut MultiBufs) -> &[u8];
336       }
337
338       // C++ types and signatures exposed to Rust.
339       unsafe extern "C++" {
340           include!("build/rust/tests/test_cxx_rust/include/client_blobstore.h");
341
342           type client_blobstore;
343
344           fn blobstore_client_new() -> UniquePtr<client_blobstore>;
345           fn put_buf(&self, parts: &mut MultiBufs) -> u64;
346           fn add_tag(&self, blobid: u64, add_tag: &str);
347           fn get_metadata(&self, blobid: u64) -> Metadata_Blob;
348       }
349   }
350
351   // An iterator over contiguous chunks of a discontiguous file object.
352   //
353   // Toy implementation uses a Vec<Vec<u8>> but in reality this might be iterating
354   // over some more complex Rust data structure like a rope, or maybe loading
355   // chunks lazily from somewhere.
356   /// pub struct MultiBufs
357   pub struct MultiBufs {
358       chunks: Vec<Vec<u8>>,
359       pos: usize,
360   }
361   /// pub fn next_chunk
362   pub fn next_chunk(buf: &mut MultiBufs) -> &[u8] {
363       let next = buf.chunks.get(buf.pos);
364       buf.pos += 1;
365       next.map_or(&[], Vec::as_slice)
366   }
367
368   /// fn main()
369   fn main() {
370       let client = ffi::blobstore_client_new();
371
372       // Upload a blob.
373       let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()];
374       let mut buf = MultiBufs { chunks, pos: 0 };
375       let blobid = client.put_buf(&mut buf);
376       println!("This is a test for Rust call cpp:");
377       println!("blobid = {}", blobid);
378
379       // Add a add_tag.
380       client.add_tag(blobid, "rust");
381
382       // Read back the tags.
383       let get_metadata = client.get_metadata(blobid);
384       println!("tags = {:?}", get_metadata.tags);
385   }
386   ```
387
3884. 添加构建文件BUILD.gn。使用CXX将main.rs转换成lib.rs.hlib.rs.cc,同时将产物作为test_cxx_rust_staticlib的源码,编译Rust源码main.rs并将test_cxx_rust_staticlib依赖进来。
389
390   ```
391   import("//build/ohos.gni")
392
393   rust_cxx("test_cxx_rust_gen") {
394     sources = [ "src/main.rs" ]
395   }
396
397   ohos_static_library("test_cxx_rust_staticlib") {
398     sources = [ "src/client_blobstore.cpp" ]
399     sources += get_target_outputs(":test_cxx_rust_gen")
400     include_dirs = [
401       "${target_gen_dir}",
402       "//third_party/rust/cxx/v1/crate/include",
403       "include",
404     ]
405     deps = [
406       ":test_cxx_rust_gen",
407       "//build/rust:cxx_cppdeps",
408     ]
409   }
410
411   ohos_rust_executable("test_cxx_rust") {
412     sources = [ "src/main.rs" ]
413     deps = [
414       ":test_cxx_rust_staticlib",
415       "//build/rust:cxx_rustdeps",
416     ]
417   }
418   ```
419
420**调测验证**
421![rust_call_cpp](./figures/rust_call_cpp.png)