html制作網(wǎng)站的步驟網(wǎng)絡(luò)服務(wù)包括
本示例演示了一個(gè)調(diào)用blobstore服務(wù)的C++客戶(hù)端的Rust應(yīng)用程序。事實(shí)上,我們會(huì)看到兩個(gè)方向的調(diào)用:Rust到C++以及C++到Rust。對(duì)于您自己的用例,您可能只需要其中一個(gè)方向。
示例中涉及的所有代碼都顯示在此頁(yè)面上,但它也以可運(yùn)行的形式提供在demo目錄中https://github.com/dtolnay/cxx.要直接嘗試,請(qǐng)從該目錄運(yùn)行cargo run。
共享結(jié)構(gòu)、不透明類(lèi)型和函數(shù)已經(jīng)在上一篇文章中敘述,不清楚的可以先去看一下。
一、創(chuàng)建項(xiàng)目
我們?cè)诿钚兄袆?chuàng)建一個(gè)空白的Cargo項(xiàng)目:
cargo new cxx-demo
編輯Cargo.toml文件,添加對(duì)cxx的依賴(lài):
[dependencies]
cxx = "1.0"
二、定義語(yǔ)言邊界
CXX依賴(lài)于對(duì)每種語(yǔ)言向另一種語(yǔ)言公開(kāi)的函數(shù)簽名的描述。您可以在Rust模塊中使用extern塊提供此描述,該模塊用#[cxx::bridge]屬性宏注釋。
我們?cè)陧?xiàng)目的main.rs文件的頂部添加該內(nèi)容:
#[cxx::bridge]
mod ffi {
}
該內(nèi)容將是FFI邊界雙方需要達(dá)成一致的所有內(nèi)容。
三、從Rust調(diào)用C++函數(shù)
讓我們獲取一個(gè)C++blobstore客戶(hù)端的實(shí)例,一個(gè)在C++中定義的類(lèi)blobstore client。
我們將把BlobstreClient視為CXX分類(lèi)中的不透明類(lèi)型,這樣Rust就不需要對(duì)其實(shí)現(xiàn)做出任何假設(shè),甚至不需要對(duì)它的大小或?qū)R方式做出任何假設(shè)。一般來(lái)說(shuō),C++類(lèi)型可能有一個(gè)與Rust的move語(yǔ)義不兼容的move構(gòu)造函數(shù),或者可能包含Rust的借用系統(tǒng)無(wú)法建模的內(nèi)部引用。盡管有其他選擇,但在FFI邊界上不關(guān)心任何此類(lèi)事情的最簡(jiǎn)單方法是將其視為不透明,不需要了解類(lèi)型。
不透明類(lèi)型只能在間接后面操作,如引用&、Rust Box或UniquePtr(std::unique_ptr的Rust綁定)。我們將添加一個(gè)函數(shù),通過(guò)該函數(shù),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() -> UniquePtr<BlobstoreClient>;}
}fn main() {let client = ffi::new_blobstore_client();
}
即使CXX自動(dòng)執(zhí)行靜態(tài)斷言,確保簽名與C++中聲明的完全匹配,我們?nèi)匀恍枰_保鍵入的簽名是準(zhǔn)確的。比如new_blobstore_client函數(shù)如果會(huì)發(fā)生意外(如內(nèi)存錯(cuò)誤)必須用unsafe標(biāo)記。這次是在一個(gè)安全的extern“C++”塊中,因?yàn)槌绦騿T不再需要對(duì)簽名進(jìn)行任何安全聲明。
四、添加C++代碼
在CXX與Cargo的集成中,默認(rèn)情況下,所有#include路徑都以單元包(crate)名稱(chēng)開(kāi)頭。這就是為什么我們看到 include!(“cxx-demowj/include/blobstore.h”) ——我們將把C++頭文件放在Rust單元包內(nèi)的相對(duì)路徑include/blostore.h處。如果根據(jù)Cargo.toml中的name字段,你的crate的名稱(chēng)不是cxx-demo,那么在本教程中,你需要在所有地方使用這個(gè)名稱(chēng)來(lái)代替cxx-demo。
// include/blobstore.h#pragma once
#include <memory>class BlobstoreClient {
public:BlobstoreClient();
};std::unique_ptr<BlobstoreClient> new_blobstore_client();// src/blobstore.cc#include "cxx-demo/include/blobstore.h"BlobstoreClient::BlobstoreClient() {}std::unique_ptr<BlobstoreClient> new_blobstore_client() {return std::unique_ptr<BlobstoreClient>(new BlobstoreClient());
}
使用std::make_unique也可以,只要你將std(“c++14”)傳遞給c++編譯器,如稍后所述。
include/和src/中的位置并不重要;只要在整個(gè)項(xiàng)目中使用正確的路徑,就可以將C++代碼放置在單元包中的任何其他位置。
請(qǐng)注意,CXX不會(huì)查看這些文件中的任何一個(gè)。你可以自由地在這里放任意的C++代碼, #include你自主的庫(kù)等等。CXX庫(kù)所做的就是針對(duì)您在頭文件中提供的內(nèi)容發(fā)出靜態(tài)斷言。
五、用Cargo編譯C++代碼
Cargo有一個(gè)適合編譯非Rust代碼的構(gòu)建腳本功能。
我們需要在Cargo.toml中引入對(duì)CXX的C++代碼生成器的新的構(gòu)建時(shí)依賴(lài):
# Cargo.toml[dependencies]
cxx = "1.0"[build-dependencies]
cxx-build = "1.0"
然后在Cargo.toml旁邊添加一個(gè)build.rs構(gòu)建腳本,以運(yùn)行cxx構(gòu)建代碼生成器和C++編譯器。相關(guān)參數(shù)是包含cxx::bridge語(yǔ)言邊界定義的Rust源文件的路徑,以及在Rust crate構(gòu)建過(guò)程中要編譯的任何其他C++源文件的道路。
// build.rsfn main() {cxx_build::bridge("src/main.rs").file("src/blobstore.cc").compile("cxx-demo");println!("cargo:rerun-if-changed=src/main.rs");println!("cargo:rerun-if-changed=src/blobstore.cc");println!("cargo:rerun-if-changed=include/blobstore.h");
}
他的build.rs也是您設(shè)置C++編譯器標(biāo)志的地方,例如,如果您想從C++14訪(fǎng)問(wèn)std::make_unique。
cxx_build::bridge("src/main.rs").file("src/blobstore.cc").std("c++14").compile("cxx-demo");
盡管還沒(méi)有做任何有用的事情,該項(xiàng)目現(xiàn)在應(yīng)該能夠成功構(gòu)建和運(yùn)行。命令行輸入命令如下:
<cxx-demo路徑提示符> cargo runCompiling cxx-demo v0.1.0Finished dev [unoptimized + debuginfo] target(s) in 0.34sRunning `target/debug/cxx-demo`<cxx-demo路徑提示符>
六、從C++調(diào)用Rust函數(shù)
我們的C++blobstore支持不連續(xù)緩沖區(qū)上傳的put操作。例如,我們可能正在上傳一個(gè)循環(huán)緩沖區(qū)的快照,該緩沖區(qū)往往由2個(gè)部分組成,或者由于其他原因(如繩索數(shù)據(jù)結(jié)構(gòu))而分散在內(nèi)存中的文件片段。
我們將通過(guò)在連續(xù)的借用塊上傳遞迭代器來(lái)表達(dá)這一點(diǎn)。這與廣泛使用的字節(jié)箱的Buf特性的API非常相似。在put過(guò)程中,我們將讓C++回調(diào)到Rust中,以獲取上傳的連續(xù)塊(所有塊都沒(méi)有在語(yǔ)言邊界上進(jìn)行復(fù)制或分配)。實(shí)際上,C++客戶(hù)端可能包含一些復(fù)雜的塊批處理或并行上傳,所有這些都與之相關(guān)。
// 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() -> UniquePtr<BlobstoreClient>;fn put(&self, parts: &mut MultiBuf) -> u64;}
}
任何具有self參數(shù)的簽名(等同C++的this)都被認(rèn)為是一個(gè)方法/非靜態(tài)成員函數(shù)。如果周?chē)膃xtern塊中只有一個(gè)類(lèi)型,則它將是該類(lèi)型的方法。如果有多個(gè)類(lèi)型,您可以通過(guò)在參數(shù)列表中編寫(xiě)self:&BlostreClient來(lái)區(qū)分方法屬于哪一個(gè)。
像往常一樣,現(xiàn)在我們需要提供extern“Rust”塊聲明的所有內(nèi)容的Rust定義,以及extern“C++”塊宣布的新簽名的C++定義。
// src/main.rs// An iterator over contiguous chunks of a discontiguous file object. Toy
// implementation uses a Vec<Vec<u8>> 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: Vec<Vec<u8>>,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中,我們可以調(diào)用Rust next_chunk函數(shù),該函數(shù)通過(guò)CXX代碼生成器生成的頭部文件main.rs.h暴露給C++。在CXX的Cargo集成中,這個(gè)生成的頭文件有一個(gè)包含crate名稱(chēng)、crate中Rust源文件的相對(duì)路徑和.rs.h擴(kuò)展名的路徑。
// 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 caller's chunk iterator.std::string contents;while (true) {auto chunk = next_chunk(buf);if (chunk.size() == 0) {break;}contents.append(reinterpret_cast<const char *>(chunk.data()), chunk.size());}// Pretend we did something useful to persist the data.auto blobid = std::hash<std::string>{}(contents);return blobid;
}
現(xiàn)在可以使用了
// src/main.rsfn main() {let client = ffi::new_blobstore_client();// Upload a blob.let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()];let mut buf = MultiBuf { chunks, pos: 0 };let blobid = client.put(&mut buf);println!("blobid = {}", blobid);
}
運(yùn)行信息如下:
cxx-demo$ cargo runCompiling cxx-demo v0.1.0Finished dev [unoptimized + debuginfo] target(s) in 0.41sRunning `target/debug/cxx-demo`blobid = 9851996977040795552
七、插曲:產(chǎn)生了什么?
對(duì)于好奇的人來(lái)說(shuō),很容易了解CXX為使這些函數(shù)調(diào)用工作所做的幕后工作。在CXX的正常使用過(guò)程中,您不需要這樣做,但就本教程而言,這可能具有教育意義。
CXX包含兩個(gè)代碼生成器:一個(gè)Rust生成器(即CXX::bridge屬性過(guò)程宏)和一個(gè)C++生成器。
Rust生成的代碼
通過(guò)cargo-expand可以最容易地查看程序宏的輸出。然后運(yùn)行cargo expand ::ffi宏展開(kāi)mod ffi模塊。
cxx-demo$ cargo install cargo-expand
cxx-demo$ cargo expand ::ffi
您將看到一些非常令人不快的代碼,涉及#[repr(C)]、#[repr?], #[link_name] 和 #[export_name].。
八、C++生成的代碼
為了調(diào)試方便,cxx_build將所有生成的C++代碼鏈接到Cargo在target/cxxbridge/下的目標(biāo)目錄中。
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
在這些文件中,您將看到語(yǔ)言邊界中存在的任何CXX Rust類(lèi)型的聲明或模板(如Rust::Slicefor&[T])以及與extern函數(shù)對(duì)應(yīng)的extern“C”簽名。
如果CXX C++代碼生成器更適合您的工作流程,它也可以作為一個(gè)獨(dú)立的可執(zhí)行文件提供,將生成的代碼輸出到stdout。
cxx-demo$ cargo install cxxbridge-cmd
cxx-demo$ cxxbridge src/main.rs
九、共享數(shù)據(jù)結(jié)構(gòu)
到目前為止,上述兩個(gè)方向的調(diào)用只使用了不透明類(lèi)型,而不是共享結(jié)構(gòu)。
共享結(jié)構(gòu)是數(shù)據(jù)結(jié)構(gòu),其完整定義對(duì)兩種語(yǔ)言都是可見(jiàn)的,從而可以通過(guò)值跨語(yǔ)言傳遞它們。共享結(jié)構(gòu)轉(zhuǎn)換為C++聚合初始化兼容結(jié)構(gòu),與Rust結(jié)構(gòu)的布局完全匹配。
作為此演示的最后一步,我們將使用共享結(jié)構(gòu)體BlobMetadata在Rust應(yīng)用程序和C++blobstore客戶(hù)端之間傳遞有關(guān)blob的元數(shù)據(jù)。
// src/main.rs#[cxx::bridge]
mod ffi {struct BlobMetadata {size: usize,tags: Vec<String>,}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![b"fearless".to_vec(), b"concurrency".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.h"struct 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_ptr<impl> 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::set<std::string> tags;};std::unordered_map<uint64_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;
}
運(yùn)行命令:
cxx-demo$ cargo runRunning `target/debug/cxx-demo`blobid = 9851996977040795552
tags = ["rust"]
現(xiàn)在您已經(jīng)看到了本教程中涉及的所有代碼。它可以在演示目錄中以可運(yùn)行的形式一起使用https://github.com/dtolnay/cxx.您可以直接運(yùn)行它,而無(wú)需從該目錄運(yùn)行cargo run來(lái)完成上述步驟。
十、結(jié)束語(yǔ)
CXX的主要貢獻(xiàn)是它為你提供了Rust與C++的互操作性,在這種互操作性中,你編寫(xiě)的所有Rust端代碼看起來(lái)都像是在寫(xiě)普通的Rust,而C++端看起來(lái)也像是在編寫(xiě)普通的C++。
在文中,您已經(jīng)看到,所涉及的代碼都不像C,也不像通常危險(xiǎn)的“FFI膠水”,容易發(fā)生泄漏或內(nèi)存安全缺陷。
由不透明類(lèi)型、共享類(lèi)型和關(guān)鍵標(biāo)準(zhǔn)庫(kù)類(lèi)型綁定組成的表達(dá)系統(tǒng)使API能夠在語(yǔ)言邊界上進(jìn)行設(shè)計(jì),從而獲取接口的正確所有權(quán)和借用契約。
CXX發(fā)揮了Rust類(lèi)型系統(tǒng)和C++類(lèi)型系統(tǒng)的優(yōu)勢(shì)以及程序員的直覺(jué)。一個(gè)在沒(méi)有Rust背景的C++端或沒(méi)有C++背景的Rust端工作的人,將能夠運(yùn)用他們對(duì)語(yǔ)言開(kāi)發(fā)的所有常見(jiàn)直覺(jué)和最佳實(shí)踐來(lái)維護(hù)正確的FFI。