南宁网站改版,卡盟怎么做网站,一天挣5000元的偏门路子,九洲建设官方网站本示例演示了一个调用blobstore服务的C客户端的Rust应用程序。事实上#xff0c;我们会看到两个方向的调用#xff1a;Rust到C以及C到Rust。对于您自己的用例#xff0c;您可能只需要其中一个方向。 示例中涉及的所有代码都显示在此页面上#xff0c;但它也以可运行的形式提…本示例演示了一个调用blobstore服务的C客户端的Rust应用程序。事实上我们会看到两个方向的调用Rust到C以及C到Rust。对于您自己的用例您可能只需要其中一个方向。 示例中涉及的所有代码都显示在此页面上但它也以可运行的形式提供在demo目录中https://github.com/dtolnay/cxx.要直接尝试请从该目录运行cargo run。 共享结构、不透明类型和函数已经在上一篇文章中叙述不清楚的可以先去看一下。
一、创建项目
我们在命令行中创建一个空白的Cargo项目 cargo new cxx-demo 编辑Cargo.toml文件添加对cxx的依赖
[dependencies]
cxx 1.0二、定义语言边界
CXX依赖于对每种语言向另一种语言公开的函数签名的描述。您可以在Rust模块中使用extern块提供此描述该模块用#[cxx:bridge]属性宏注释。 我们在项目的main.rs文件的顶部添加该内容
#[cxx::bridge]
mod ffi {
}该内容将是FFI边界双方需要达成一致的所有内容。
三、从Rust调用C函数
让我们获取一个Cblobstore客户端的实例一个在C中定义的类blobstore client。 我们将把BlobstreClient视为CXX分类中的不透明类型这样Rust就不需要对其实现做出任何假设甚至不需要对它的大小或对齐方式做出任何假设。一般来说C类型可能有一个与Rust的move语义不兼容的move构造函数或者可能包含Rust的借用系统无法建模的内部引用。尽管有其他选择但在FFI边界上不关心任何此类事情的最简单方法是将其视为不透明不需要了解类型。 不透明类型只能在间接后面操作如引用、Rust Box或UniquePtrstd:unique_ptr的Rust绑定。我们将添加一个函数通过该函数C可以向Rust返回std:unique_ptr。
// src/main.rs#[cxx::bridge]
mod ffi {unsafe extern C {include!(cxx-demo/include/blobstore.h);type BlobstoreClient;fn new_blobstore_client() - UniquePtrBlobstoreClient;}
}fn main() {let client ffi::new_blobstore_client();
}即使CXX自动执行静态断言确保签名与C中声明的完全匹配我们仍然需要确保键入的签名是准确的。比如new_blobstore_client函数如果会发生意外如内存错误必须用unsafe标记。这次是在一个安全的extern“C”块中因为程序员不再需要对签名进行任何安全声明。
四、添加C代码
在CXX与Cargo的集成中默认情况下所有#include路径都以单元包(crate)名称开头。这就是为什么我们看到 include!(“cxx-demowj/include/blobstore.h”) ——我们将把C头文件放在Rust单元包内的相对路径include/blostore.h处。如果根据Cargo.toml中的name字段你的crate的名称不是cxx-demo那么在本教程中你需要在所有地方使用这个名称来代替cxx-demo。
// include/blobstore.h#pragma once
#include memoryclass BlobstoreClient {
public:BlobstoreClient();
};std::unique_ptrBlobstoreClient new_blobstore_client();// src/blobstore.cc#include cxx-demo/include/blobstore.hBlobstoreClient::BlobstoreClient() {}std::unique_ptrBlobstoreClient new_blobstore_client() {return std::unique_ptrBlobstoreClient(new BlobstoreClient());
}使用std::make_unique也可以只要你将std(“c14”)传递给c编译器如稍后所述。 include/和src/中的位置并不重要只要在整个项目中使用正确的路径就可以将C代码放置在单元包中的任何其他位置。 请注意CXX不会查看这些文件中的任何一个。你可以自由地在这里放任意的C代码 #include你自主的库等等。CXX库所做的就是针对您在头文件中提供的内容发出静态断言。
五、用Cargo编译C代码
Cargo有一个适合编译非Rust代码的构建脚本功能。 我们需要在Cargo.toml中引入对CXX的C代码生成器的新的构建时依赖
# Cargo.toml[dependencies]
cxx 1.0[build-dependencies]
cxx-build 1.0然后在Cargo.toml旁边添加一个build.rs构建脚本以运行cxx构建代码生成器和C编译器。相关参数是包含cxx::bridge语言边界定义的Rust源文件的路径以及在Rust crate构建过程中要编译的任何其他C源文件的道路。
// build.rsfn main() {cxx_build::bridge(src/main.rs).file(src/blobstore.cc).compile(cxx-demo);println!(cargo:rerun-if-changedsrc/main.rs);println!(cargo:rerun-if-changedsrc/blobstore.cc);println!(cargo:rerun-if-changedinclude/blobstore.h);
}他的build.rs也是您设置C编译器标志的地方例如如果您想从C14访问std::make_unique。 cxx_build::bridge(src/main.rs).file(src/blobstore.cc).std(c14).compile(cxx-demo);尽管还没有做任何有用的事情该项目现在应该能够成功构建和运行。命令行输入命令如下
cxx-demo路径提示符 cargo runCompiling cxx-demo v0.1.0Finished dev [unoptimized debuginfo] target(s) in 0.34sRunning target/debug/cxx-democxx-demo路径提示符六、从C调用Rust函数
我们的Cblobstore支持不连续缓冲区上传的put操作。例如我们可能正在上传一个循环缓冲区的快照该缓冲区往往由2个部分组成或者由于其他原因如绳索数据结构而分散在内存中的文件片段。 我们将通过在连续的借用块上传递迭代器来表达这一点。这与广泛使用的字节箱的Buf特性的API非常相似。在put过程中我们将让C回调到Rust中以获取上传的连续块所有块都没有在语言边界上进行复制或分配。实际上C客户端可能包含一些复杂的块批处理或并行上传所有这些都与之相关。
// src/main.rs#[cxx::bridge]
mod ffi {extern Rust {type MultiBuf;fn next_chunk(buf: mut MultiBuf) - [u8];}unsafe extern C {include!(cxx-demo/include/blobstore.h);type BlobstoreClient;fn new_blobstore_client() - UniquePtrBlobstoreClient;fn put(self, parts: mut MultiBuf) - u64;}
}任何具有self参数的签名等同C的this都被认为是一个方法/非静态成员函数。如果周围的extern块中只有一个类型则它将是该类型的方法。如果有多个类型您可以通过在参数列表中编写self:BlostreClient来区分方法属于哪一个。 像往常一样现在我们需要提供extern“Rust”块声明的所有内容的Rust定义以及extern“C”块宣布的新签名的C定义。
// src/main.rs// An iterator over contiguous chunks of a discontiguous file object. Toy
// implementation uses a VecVecu8 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 MultiBuf {chunks: VecVecu8,pos: usize,
}pub fn next_chunk(buf: mut MultiBuf) - [u8] {let next buf.chunks.get(buf.pos);buf.pos 1;next.map_or([], Vec::as_slice)
}// include/blobstore.hstruct MultiBuf;class BlobstoreClient {
public:BlobstoreClient();uint64_t put(MultiBuf buf) const;
};在blobstre.cc中我们可以调用Rust next_chunk函数该函数通过CXX代码生成器生成的头部文件main.rs.h暴露给C。在CXX的Cargo集成中这个生成的头文件有一个包含crate名称、crate中Rust源文件的相对路径和.rs.h扩展名的路径。
// src/blobstore.cc#include cxx-demo/include/blobstore.h
#include cxx-demo/src/main.rs.h
#include functional
#include string// Upload a new blob and return a blobid that serves as a handle to the blob.
uint64_t BlobstoreClient::put(MultiBuf buf) const {// Traverse the callers chunk iterator.std::string contents;while (true) {auto chunk next_chunk(buf);if (chunk.size() 0) {break;}contents.append(reinterpret_castconst char *(chunk.data()), chunk.size());}// Pretend we did something useful to persist the data.auto blobid std::hashstd::string{}(contents);return blobid;
}现在可以使用了
// src/main.rsfn main() {let client ffi::new_blobstore_client();// Upload a blob.let chunks vec![bfearless.to_vec(), bconcurrency.to_vec()];let mut buf MultiBuf { chunks, pos: 0 };let blobid client.put(mut buf);println!(blobid {}, blobid);
}运行信息如下
cxx-demo$ cargo runCompiling cxx-demo v0.1.0Finished dev [unoptimized debuginfo] target(s) in 0.41sRunning target/debug/cxx-demoblobid 9851996977040795552七、插曲产生了什么
对于好奇的人来说很容易了解CXX为使这些函数调用工作所做的幕后工作。在CXX的正常使用过程中您不需要这样做但就本教程而言这可能具有教育意义。 CXX包含两个代码生成器一个Rust生成器即CXX:bridge属性过程宏和一个C生成器。 Rust生成的代码 通过cargo-expand可以最容易地查看程序宏的输出。然后运行cargo expand ::ffi宏展开mod ffi模块。
cxx-demo$ cargo install cargo-expand
cxx-demo$ cargo expand ::ffi您将看到一些非常令人不快的代码涉及#[reprC]、#[repr©], #[link_name] 和 #[export_name].。
八、C生成的代码
为了调试方便cxx_build将所有生成的C代码链接到Cargo在target/cxxbridge/下的目标目录中。
cxx-demo$ exa -T target/cxxbridge/
target/cxxbridge
├── cxx-demo
│ └── src
│ ├── main.rs.cc - ../../../debug/build/cxx-demo-11c6f678ce5c3437/out/cxxbridge/sources/cxx-demo/src/main.rs.cc
│ └── main.rs.h - ../../../debug/build/cxx-demo-11c6f678ce5c3437/out/cxxbridge/include/cxx-demo/src/main.rs.h
└── rust└── cxx.h - ~/.cargo/registry/src/github.com-1ecc6299db9ec823/cxx-1.0.0/include/cxx.h在这些文件中您将看到语言边界中存在的任何CXX Rust类型的声明或模板如Rust:Slicefor[T]以及与extern函数对应的extern“C”签名。 如果CXX C代码生成器更适合您的工作流程它也可以作为一个独立的可执行文件提供将生成的代码输出到stdout。
cxx-demo$ cargo install cxxbridge-cmd
cxx-demo$ cxxbridge src/main.rs九、共享数据结构
到目前为止上述两个方向的调用只使用了不透明类型而不是共享结构。 共享结构是数据结构其完整定义对两种语言都是可见的从而可以通过值跨语言传递它们。共享结构转换为C聚合初始化兼容结构与Rust结构的布局完全匹配。 作为此演示的最后一步我们将使用共享结构体BlobMetadata在Rust应用程序和Cblobstore客户端之间传递有关blob的元数据。
// src/main.rs#[cxx::bridge]
mod ffi {struct BlobMetadata {size: usize,tags: VecString,}extern Rust {// ...}unsafe extern C {// ...fn tag(self, blobid: u64, tag: str);fn metadata(self, blobid: u64) - BlobMetadata;}
}fn main() {let client ffi::new_blobstore_client();// Upload a blob.let chunks vec![bfearless.to_vec(), bconcurrency.to_vec()];let mut buf MultiBuf { chunks, pos: 0 };let blobid client.put(mut buf);println!(blobid {}, blobid);// Add a tag.client.tag(blobid, rust);// Read back the tags.let metadata client.metadata(blobid);println!(tags {:?}, metadata.tags);
}// include/blobstore.h#pragma once
#include rust/cxx.hstruct MultiBuf;
struct BlobMetadata;class BlobstoreClient {
public:BlobstoreClient();uint64_t put(MultiBuf buf) const;void tag(uint64_t blobid, rust::Str tag) const;BlobMetadata metadata(uint64_t blobid) const;private:class impl;std::shared_ptrimpl impl;
};// src/blobstore.cc#include cxx-demo/include/blobstore.h
#include cxx-demo/src/main.rs.h
#include algorithm
#include functional
#include set
#include string
#include unordered_map// Toy implementation of an in-memory blobstore.
//
// In reality the implementation of BlobstoreClient could be a large
// complex C library.
class BlobstoreClient::impl {friend BlobstoreClient;using Blob struct {std::string data;std::setstd::string tags;};std::unordered_mapuint64_t, Blob blobs;
};BlobstoreClient::BlobstoreClient() : impl(new class BlobstoreClient::impl) {}// Add tag to an existing blob.
void BlobstoreClient::tag(uint64_t blobid, rust::Str tag) const {impl-blobs[blobid].tags.emplace(tag);
}// Retrieve metadata about a blob.
BlobMetadata BlobstoreClient::metadata(uint64_t blobid) const {BlobMetadata metadata{};auto blob impl-blobs.find(blobid);if (blob ! impl-blobs.end()) {metadata.size blob-second.data.size();std::for_each(blob-second.tags.cbegin(), blob-second.tags.cend(),[](auto t) { metadata.tags.emplace_back(t); });}return metadata;
}运行命令
cxx-demo$ cargo runRunning target/debug/cxx-demoblobid 9851996977040795552
tags [rust]现在您已经看到了本教程中涉及的所有代码。它可以在演示目录中以可运行的形式一起使用https://github.com/dtolnay/cxx.您可以直接运行它而无需从该目录运行cargo run来完成上述步骤。
十、结束语
CXX的主要贡献是它为你提供了Rust与C的互操作性在这种互操作性中你编写的所有Rust端代码看起来都像是在写普通的Rust而C端看起来也像是在编写普通的C。 在文中您已经看到所涉及的代码都不像C也不像通常危险的“FFI胶水”容易发生泄漏或内存安全缺陷。 由不透明类型、共享类型和关键标准库类型绑定组成的表达系统使API能够在语言边界上进行设计从而获取接口的正确所有权和借用契约。 CXX发挥了Rust类型系统和C类型系统的优势以及程序员的直觉。一个在没有Rust背景的C端或没有C背景的Rust端工作的人将能够运用他们对语言开发的所有常见直觉和最佳实践来维护正确的FFI。