Zenoh 是一個高效能、零額外開銷(zero-overhead)的 pub/sub(發布/訂閱)、儲存/查詢(store/query)與計算(compute)協定,統一了動態資料(data in motion)、靜態資料(data at rest)與計算工作。核心實作採用 Rust 以確保效能與記憶體安全,而 Zenoh 透過 zenoh-c 專案提供完整的 C 綁定,以實現與 C/C++ 應用程序的無縫整合。
接下來的三篇系列文章探討使此實現成真的複雜綁定生成系統:
zenoh-cpp
如何建立在這些基礎上,提供現代 C++ API為 Rust 函式庫創建 C 綁定面臨獨特挑戰:
Zenoh-c 透過 多層架構(multi-layered architecture) 自動從 Rust 程式碼產生安全且高效的 C API 來克服這些挑戰。
zenoh-c
專案以結構化方式實現,包含五大主要組件:
build.rs
)整個流程的核心是 build.rs
,負責指揮整個建置管線:
fn main() {
buildrs::opaque_types_generator::generate_opaque_types();
buildrs::cbindgen_generator::generate_c_headers();
// 額外的設定與驗證
}
此建置腳本於 Rust 編譯階段執行,完成多項關鍵任務:
cbindgen
zenoh-c
最具創新性的一環是其 不透明型別生成系統(buildrs/opaque_types_generator.rs
),解決 Rust 至 C FFI 的核心問題:如何在不破壞安全性(safety)與 ABI 穩定性(ABI stability)的情況下expose Rust 型別。
直接暴露 Rust 結構體給 C 將會:
Zenoh 使用 基於編譯的內省 方式抽取型別metadata:
get_opaque_type_data!
生成人為錯誤,其中包涵對齊(alignment)和大小(size)資訊。#[macro_export]
macro_rules! get_opaque_type_data {
($src_type:ty, $name:ident) => {
const _: () = {
const ALIGN: usize = std::mem::align_of::<$src_type>();
const SIZE: usize = std::mem::size_of::<$src_type>();
panic!("type: {}, align: {}, size: {}", stringify!($name), ALIGN, SIZE);
};
};
}
啟用 panic
feature 時,該巨集會產生類似錯誤訊息:
type: z_owned_bytes_t, align: 8, size: 32
建置系統會用正則表達式解析這些故意產生的錯誤:
// 計算編譯錯誤總數以作驗證
let total_error_count = data_in
.lines()
.filter(|line| line.starts_with("error[E"))
.count();
// 利用 regex 抽出型別資訊
let re = Regex::new(r"type: (\w+), align: (\d+), size: (\d+)").unwrap();
for (_, [type_name, align, size]) in re.captures_iter(&data_in).map(|c| c.extract()) {
// 依據抽取的大小與對齊生成 C 相容結構
// ...
}
// 確保所有錯誤皆有解析成功
if good_error_count != total_error_count {
panic!("Failed to parse {} out of {} compilation errors",
total_error_count - good_error_count, total_error_count);
}
此驗證確保每一則編譯錯誤皆成功對應到型別定義,確保生成綁定的完整性。
Rust 中的宣告
/// 一個 Zenoh 資料型
get_opaque_type_data!(ZBytes, z_owned_bytes_t);
get_opaque_type_data!(ZBytes, z_loaned_bytes_t);
decl_c_type! {
owned(z_owned_bytes_t, ZBytes),
loaned(z_loaned_bytes_t),
}
相容 C 的結構
// 產生於 zenoh-c/src/opaque_types/mod.rs
#[repr(C, align(8))]
#[rustfmt::skip]
pub struct z_owned_bytes_t {
_0: [u8; 32], // 由編譯內省決定的大小
}
FFI 函式實作
/// 在提供的未初始化記憶體位置建構所擁有的淺層複本
#[no_mangle]
extern "C" fn z_bytes_clone(dst: &mut MaybeUninit<z_owned_bytes_t>, this: &z_loaned_bytes_t) {
dst.as_rust_type_mut_uninit()
.write(this.as_rust_type_ref().clone());
}
/// 釋放資源,重設為墓碑值
#[no_mangle]
extern "C" fn z_bytes_drop(this_: &mut z_moved_bytes_t) {
let _ = this_.take_rust_type();
}
/// 借用資料
#[no_mangle]
unsafe extern "C" fn z_bytes_loan(this: &z_owned_bytes_t) -> &z_loaned_bytes_t {
this.as_rust_type_ref().as_loaned_c_type_ref()
}
此作法確保 C 端可為 Rust 型別正確分配大小並對齊的記憶體,且不暴露實作細節。
為支援 Rust 的移動語意,zenoh-c
生成「已移動(moved)」的變種:
#[repr(C)]
pub struct z_moved_bytes_t {
_this: z_owned_bytes_t,
}
impl TakeCType for z_moved_bytes_t {
type CType = z_owned_bytes_t;
fn take_c_type(&mut self) -> Self::CType {
std::mem::replace(&mut self._this, z_owned_bytes_t::gravestone())
}
}
使 Rust 與 C 間的所有權轉移安全。
本專案使用 cbindgen 來自動生成 C 標頭檔。以下為範例 cbindgen.toml
配置:
language = "C"
style = "both"
usize_is_size_t = true
[fn]
prefix = "ZENOHC_API"
[enum]
rename_variants = "ScreamingSnakeCase"
prefix_with_name = true
cbindgen
生成原始標頭檔fix_cbindgen()
修正格式及瑕疵)C 語言不支持模板(templates)或泛型(generics),但 Zenoh 需提供如 z_drop()
、z_move()
、z_clone()
等多型操作。解決方式是 C11 的 _Generic
(C)和函式多載(function overloading,C++)。
#define z_drop(x) \
_Generic((x), \
z_owned_session_t* : z_session_drop, \
z_owned_subscriber_t* : z_subscriber_drop, \
z_owned_publisher_t* : z_publisher_drop \
)(x)
inline void z_drop(z_owned_session_t* x) { z_session_drop(x); }
inline void z_drop(z_owned_subscriber_t* x) { z_subscriber_drop(x); }
inline void z_drop(z_owned_publisher_t* x) { z_publisher_drop(x); }
巨集產生器透過正則表達式解析生成的標頭,如:
let re = Regex::new(r"(\w+)_drop\(struct (\w+) *(\w+)\);").unwrap();
for (_, [func_name_prefix, arg_type, arg_name]) in re.captures_iter(&bindings) {
// 產生對應的 move 和 take 函式
}
確保所有必要函式皆被包含,並檢驗 API 一致性。
專案內建完整的 CMake 支援,可處理:
# 啟用特定功能
cmake -DZENOHC_BUILD_WITH_UNSTABLE_API=true ../zenoh-c
# 交叉編譯 Windows 版
cmake ../zenoh-c \
-DCMAKE_SYSTEM_NAME="Windows" \
-DZENOHC_CUSTOM_TARGET="x86_64-pc-windows-gnu"
測試套件涵蓋:
本文深度探討 Zenoh C 綁定的架構與程式碼生成。
明後兩天我們將探索:
zenoh-cpp
如何基於這些基礎打造具 RAII、模板與零成本抽象的現代 C++ API。敬請期待~