From 4db4f6be6cfd4bf17c5ed36ac86d9796d32efd13 Mon Sep 17 00:00:00 2001 From: fangting Date: Mon, 24 Apr 2023 20:07:27 +0800 Subject: [PATCH] Add README_zh.md Signed-off-by: fangting --- README_zh.md | 387 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 387 insertions(+) create mode 100644 README_zh.md diff --git a/README_zh.md b/README_zh.md new file mode 100644 index 00000000..0f598f35 --- /dev/null +++ b/README_zh.md @@ -0,0 +1,387 @@ +XX — Rust和C++之间的安全FFI +========================================= + +[github](https://github.com/dtolnay/cxx) +[crates.io](https://crates.io/crates/cxx) +[docs.rs](https://docs.rs/cxx) +[build status](https://github.com/dtolnay/cxx/actions?query=branch%3Amaster) + +这个库提供了一个**安全**的机制,可以从Rust中调用C++代码,也可以从C++中调用Rust代码,而不会像使用bindgen或cbindgen生成不安全的C风格绑定时那样出错。 +使用bindgen或cbindgen来生成不安全的C风格的绑定时,可能会出现很多问题。 + +这并不能改变100%的C++代码是不安全的这一事实。当审计一个 +项目时,你将负责审核所有不安全的Rust代码和 +*所有*的C++代码。 +这种新模式下的核心安全主张是,只对C++端进行审计,就足以发现所有问题、 +也就是说,Rust方面可以是100%安全的。 + +```toml +[dependencies] +cxx = "1.0" + +[build-dependencies] +cxx-build = "1.0" +``` + +*Compiler support: requires rustc 1.60+ and c++11 or newer*
+*[Release notes](https://github.com/dtolnay/cxx/releases)* + +
+ +## Guide + +请参阅 ****,了解教程、参考资料和实例。 +代码。 + +
+ +## Overview + +我们的想法是,我们定义FFI边界两边的签名 +嵌入到一个Rust模块中(下一节将展示一个例子)。由此 +从中,CXX得到了一个完整的边界图,以对类型和函数签名进行静态分析 +对类型和函数签名进行静态分析,以维护Rust和C++的 +不变量和要求。 + +如果所有的静态分析都通过了,那么CXX就会使用一对代码生成器来 +发出双方相关的 "extern "C "签名,以及任何必要的静态断言。 +必要的静态断言,以便在以后的构建过程中验证 +正确性。在Rust一方,这个代码生成器只是一个属性 +程序性宏。在C++方面,它可以是一个小型的Cargo构建脚本,如果你的 +如果你的构建是由Cargo管理的,它可以是一个小的Cargo构建脚本,或者对于其他的构建系统,如Bazel或Buck,我们提供了一个命令行工具来生成 +我们提供了一个命令行工具,可以生成头文件和源文件。 +应该很容易集成。 + +所产生的FFI桥的运行费用为零或可忽略不计,也就是说,没有 +复制,没有序列化,没有内存分配,也不需要运行时检查。 + +FFI的签名能够使用来自任何一方的本地类型、 +比如Rust的`String`或C++的`std::string`,Rust的`Box`或C++的 +`std::unique_ptr`,Rust的`Vec`或C++的`std::vector`,等等的任意组合。 +CXX保证ABI兼容的签名,双方都能理解,基于 +CXX保证了双方都能理解的ABI兼容的签名,基于关键的标准库类型的内置绑定,在这些类型上向另一种语言暴露一个习惯性的API。 +这些类型暴露给另一种语言的习惯性API。例如,当从Rust操作一个C++字符串时 +的时候,它的`len()`方法就变成了对C++定义的`size()`成员函数的调用。 +的调用;当从C++操作一个Rust字符串时,其`size()`成员函数 +函数调用Rust的`len()'。 + +
+ +## Example + +在这个例子中,我们正在编写一个Rust应用程序,希望利用 +大文件blobstore服务的现有C++客户端的优势。该blobstore +支持一个 "put "操作,用于不连续的缓冲区上传。例如,我们 +例如,我们可能要上传一个圆形缓冲区的快照,而这个缓冲区往往由 +2块,或者由于其他原因在内存中分散的文件片段。 + +这个例子的可运行版本是在这个 repo 的*demo*目录下提供的。 +目录下。要尝试它,从该目录中运行`cargo run`。 + +```rust +#[cxx::bridge] +mod ffi { + // Any shared structs, whose fields will be visible to both languages. + struct BlobMetadata { + size: usize, + tags: Vec, + } + + extern "Rust" { + // Zero or more opaque types which both languages can pass around but + // only Rust can see the fields. + type MultiBuf; + + // Functions implemented in Rust. + fn next_chunk(buf: &mut MultiBuf) -> &[u8]; + } + + unsafe extern "C++" { + // One or more headers with the matching C++ declarations. Our code + // generators don't read it but it gets #include'd and used in static + // assertions to ensure our picture of the FFI boundary is accurate. + include!("demo/include/blobstore.h"); + + // Zero or more opaque types which both languages can pass around but + // only C++ can see the fields. + type BlobstoreClient; + + // Functions implemented in C++. + fn new_blobstore_client() -> UniquePtr; + fn put(&self, parts: &mut MultiBuf) -> u64; + fn tag(&self, blobid: u64, tag: &str); + fn metadata(&self, blobid: u64) -> BlobMetadata; + } +} +``` + +现在我们只需在 "extern "Rust "块中提供Rust的定义,在 "extern "C++"块中提供C++的定义。 +块中的所有内容提供Rust定义,并为`extern "C++"`块中的所有内容提供C++定义,然后就可以 +安全地来回调用。 + +下面是演示中涉及到的一整套源文件的链接: + +- [demo/src/main.rs](演示/src/main.rs) +- [demo/build.rs](demo/build.rs) +- [demo/include/blobstore.h](demo/include/blobstore.h) +- [demo/src/blobstore.cc](demo/src/blobstore.cc) + +要看一下CXX代码为该例子生成的两种语言的代码 +生成器: + +```console + # run Rust code generator and print to stdout + # (requires https://github.com/dtolnay/cargo-expand) +$ cargo expand --manifest-path demo/Cargo.toml + + # run C++ code generator and print to stdout +$ cargo run --manifest-path gen/cmd/Cargo.toml -- demo/src/main.rs +``` + +
+ +## Details + +从例子中可以看出,FFI边界的语言涉及3种类型的 +项目: + +- **共享结构** —它们的字段对两种语言都是可见的。 + 写在 cxx::bridge 内的定义是唯一的真理来源。 + +- **不透明类型** —它们的字段对另一种语言是保密的。 + 这些类型不能通过FFI的值来传递,而只能在间接的后面传递、 + 比如一个引用`&`,一个Rust`Box`,或者一个`UniquePtr`。可以是一个类型别名 + 可以是一个类型别名,用于任意复杂的通用语言特定类型,这取决于你的用例。 + 你的用例。 + +- **函数** — 在任一语言中实现,可从另一语言中调用。 + 语言中调用。 + +在CXX桥的 "extern "Rust "部分中,我们列出了Rust作为源码的类型和函数。 +函数,因为Rust是真理的来源。这些都隐含地提到了 +`super`模块,CXX桥的父模块。你可以认为 +你可以把上面例子中列出的两个项目看作是`use super::MultiBuf`和 +`use super::next_chunk`,只不过是重新输出到了C++。父模块将 +直接包含简单事物的定义,或者包含相关的 +`use`语句,将它们从其他地方带入范围。 + +在`extern "C++"`部分中,我们列出了C++是真理之源的类型和函数。 +真理的来源,以及声明这些API的头文件()。在未来 +这一部分有可能从头文件中以宾根方式生成。 +但现在我们需要把签名写出来;静态断言将 +验证它们是否准确。 + +你的函数实现本身,无论是C++还是Rust,*都不*需要 +定义为 "extern "C "ABI或no/mangle。CXX会在必要的地方加入正确的垫片 +在必要的情况下,使其全部工作。 +
+ +## Comparison vs bindgen and cbindgen + +请注意,在CXX中,所有的函数签名都是重复的:它们在定义(C++或Rust中)的地方被打出来一次,而在实现的地方被打出来一次。 +它们在定义实现的地方(C++或Rust)被打出来一次,在cxx::bridge模块中又被打出来一次。 +再一次在 cxx::bridge 模块里,尽管编译时断言保证了 +这些都是同步的。这与 [bindgen] 和 [cbindgen] 不同,后者的 +这与 [bindgen] 和 [cbindgen] 不同,在后者中,函数签名由人类输入一次,工具在一种语言中消费它们,并在另一种语言中输出它们。 +语言,然后用另一种语言输出。 + +[bindgen]: https://github.com/rust-lang/rust-bindgen +[cbindgen]: https://github.com/eqrion/cbindgen/ + +这是因为CXX扮演了一个有点不同的角色。它是一个较低级别的工具 +从某种意义上说,它是一个比bindgen或cbindgen更低级的工具;你可以把它看成是 "外部 "概念的替代物。 +我们所知道的 "extern "C "签名的概念,而不是bindgen的替代品。 +而不是取代bindgen。在bindgen的基础上建立一个更高层次的 +在CXX之上建立一个更高层次的类似于bindgen的工具是合理的,它可以消耗一个C++头和/或Rust模块 +(和/或像Thrift那样的IDL)作为真理的来源,并生成cxx::bridge、 +消除了重复,同时利用CXX的静态分析安全 +的保证。 + +但要注意的是,在其他方面,CXX比bindgens更高级,有丰富的支持 +对普通标准库类型的丰富支持。在bindgen中,当我们处理 +习惯的C++ API时,我们最终会用C-style的原始指针函数来手动包装该API。 +原始指针函数,应用bindgen来获得不安全的原始指针Rust +函数,并再次复制API以在Rust中公开这些成语。 +这是一种更糟糕的重复形式,因为它从头到尾都是不安全的。 + +通过使用CXX桥梁作为语言之间的共享理解,而不是 +而不是 "外部 "C "C "式签名作为共享理解,常见的FFI用例就可以用100%安全的方式来表达。 +情况下,可以使用100%安全的代码来表达。 + +混合使用也是合理的,在95%的FFI中使用CXX桥接。 +你的FFI有95%是简单的,而剩下的一些奇怪的签名 +用bindgen和cbindgen的老式方法,如果因为某些原因CXX的静态 +限制。如果你最终采取这种方式,请提交一个问题 +这样我们就能知道有哪些方法值得让这个工具更具有 +表达能力。 + +
+ +## Cargo-based setup + +对于由Cargo协调的构建,你将使用一个构建脚本来运行 +CXX的C++代码生成器,并将生成的C++代码与任何 +其他的C++代码。 + +典型的构建脚本如下。指示行返回一个 +[`cc::Build`] 实例(来自通常广泛使用的`cc`板块),在此基础上你可以 +设置任何额外的源文件和编译器标志,如常。 + +[`cc::Build`]: https://docs.rs/cc/1.0/cc/struct.Build.html + +```toml +# Cargo.toml + +[build-dependencies] +cxx-build = "1.0" +``` + +```rust +// 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"); +} +``` + +
+ +## Non-Cargo setup + +对于在非Cargo构建中的使用,如Bazel或Buck,CXX提供了另一种方式来 +CXX提供了另一种调用C++代码生成器的方法,作为一个独立的命令行工具。该工具被 +该工具被打包为crates.io上的 "cxxbridge-cmd",或者可以从本文档中的 +*gen/cmd*目录下构建。 + +```bash +$ cargo install cxxbridge-cmd + +$ cxxbridge src/main.rs --header > path/to/mybridge.h +$ cxxbridge src/main.rs > path/to/mybridge.cc +``` + +
+ +## Safety + +请注意,这个图书馆的设计是故意限制性和 +观点! 它的目标并不是要强大到可以处理任意的 +签名。相反,这个项目是关于划出一个 +合理表达的功能集,我们可以在今天做出有用的安全保证,并可能随着时间的推移进行扩展。 +保证,并可能随着时间的推移进行扩展。你可能会发现,需要一些 +练习才能有效地使用CXX桥,因为它不会以你习惯的所有方式工作。 +习惯的方式工作。 + +确保安全的一些考虑是: + +- 根据设计,我们的配对代码生成器一起工作,控制FFI边界的两边。 + FFI的边界。通常情况下,在Rust中编写自己的 "外部 "C "块是 + 是不安全的,因为Rust编译器没有办法知道你写的签名是否与 + 你所写的签名是否真的与其他语言实现的签名相匹配。 + 语言实现的签名。有了CXX,我们就可以实现这种可见性,并且知道另一边是什么。 + 的内容。 + +- 我们的静态分析可以检测并防止不应该以值传递的类型 + 从C++到Rust中以值传递的类型,例如,因为它们可能包含 + 内部指针,而这些指针会被Rust的移动行为所破坏。 + +- 令很多人惊讶的是,Rust中的结构和C++中的 + 结构的布局/字段/对齐方式/一切都完全相同、 + 但在通过值传递时,仍然不是相同的ABI。这是一个长期存在的 + bindgen的错误,导致看起来绝对正确的代码出现segfaults + ([rust-lang/rust-bindgen#778])。CXX知道这一点,并且可以插入 + 必要的零成本的解决方法,所以请继续并 + 毫无顾虑地传递你的结构。这可以通过拥有 + 边界的两边,而不是只有一边。 + +- 模板实例化:例如,为了在Rust中展示一个UniquePtr\类型,该类型由一个真正的Truker支持。 + 为了在Rust中展示一个由真正的C++ unique\_ptr支持的类型,我们可以使用Rust trait + 来将行为连接到由其他语言执行的模板实例上。 + 其他语言执行的模板实例。 + +[rust-lang/rust-bindgen#778]: https://github.com/rust-lang/rust-bindgen/issues/778 + +
+ +## Builtin types + +除了所有的原始类型(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*文件定义的。 +中的*include/cxx.h*文件定义。你将需要在你的C++代码中包含这个头文件,当你使用这些类型时 +种类的时候,你需要在你的C++代码中加入这个头文件。 + +以下类型打算 "很快 "被支持,只是还没有实现。 +实现。我并不指望这些类型的工作会很困难,但这是一个 +但这是一个在非本机语言中为每个类型设计一个漂亮的API的问题。 + + + + + + + + + +
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>
+ +
+ +## Remaining work + +对于CXX来说,这仍然是早期阶段;我把它作为一个最小的可行产品来发布 +以收集对方向的反馈并邀请合作者。请查看 +开放的问题。 + +特别是如果你在构建或连接这些东西时遇到问题,请报告问题。 +的任何问题,请报告。我相信有一些方法可以使构建方面更加友好或 +更加健壮。 + +最后,我对Rust库的设计比C++库的设计了解得更多,所以我 +所以我希望有人能帮助我把这个项目中的C++ APIs变得更容易理解。 +任何人都有建议。 + +
+ +#### License + + +Licensed under either of Apache License, Version +2.0 or MIT license at your option. + + +
+ + +除非你明确说明,任何由你故意提交的贡献 +纳入本项目,如Apache-2.0许可中所定义、 +应按上述规定获得双重许可,没有任何附加条款或条件。 + + -- Gitee