首页
/ comprehensive-rust 课程解读:CXX Bridge 模块——用 `[cxx::bridge]` 搭建 Rust 与 C++ 的安全互操作桥梁

comprehensive-rust 课程解读:CXX Bridge 模块——用 `[cxx::bridge]` 搭建 Rust 与 C++ 的安全互操作桥梁

2026-09-09 09:52:24作者:丁柯新Fawn

CXX 是目前 Rust 与 C++ 安全互操作的主流方案,而 #[cxx::bridge] 是它的核心入口。本文以 comprehensive-rust 课程(Google Android 团队使用的 Rust 教学材料)中 src/android/interoperability/cpp/bridge.md 为主线,结合仓库内完整的 blobstore 示例,讲透 Bridge 模块的声明方式、三种区块(共享类型、extern "Rust"extern "C++")、自动生成代码的机制、跨语言类型映射,以及在 Android 构建系统中的落地方式。读完你将能独立读懂并编写一个最小的 CXX 桥接模块。

一、Bridge 模块是什么:一份跨语言的“签名契约”

CXX 与手写 FFI(直接写 extern "C")的本质区别在于:CXX 不要求你手动保证两边签名一致,而是要求你用一份权威的声明来描述两种语言之间暴露的函数签名,这份声明就是 Bridge 模块。

Bridge 模块是一个被 #[cxx::bridge] 属性宏注解的普通 Rust 模块,模块内部通过 extern 块来声明跨语言接口。课程的原文定义如下:

CXX relies on a description of the function signatures that will be exposed from each language to the other. You provide this description using extern blocks in a Rust module annotated with the #[cxx::bridge] attribute macro.

这句话拆解出三个关键点:

  1. 描述对象:从每种语言(Rust 与 C++)暴露给另一种语言的函数签名;
  2. 描述方式:模块内的 extern 块;
  3. 触发机制#[cxx::bridge] 属性宏。

当编译器看到 #[cxx::bridge] 后,CXX 会基于这份声明同时生成匹配的 Rust 类型/函数定义与 C++ 类型/函数定义,把两种语言的项目符号(symbols)对齐起来。也就是说,Bridge 模块既是“需求说明书”,也是代码生成器的“输入原料”。

二、完整示例:blobstore 的 Bridge 模块

课程的示例取自仓库中的 third_party/cxx/blobstore/src/main.rs,这是一个演示 CXX 用法的完整可运行项目。它的 Bridge 模块如下:

#[allow(unsafe_op_in_unsafe_fn)]
#[cxx::bridge(namespace = "org::blobstore")]
mod ffi {
    // Shared structs with fields visible to both languages.
    struct BlobMetadata {
        size: usize,
        tags: Vec<String>,
    }

    // Rust types and signatures exposed to C++.
    extern "Rust" {
        type MultiBuf;

        fn next_chunk(buf: &mut MultiBuf) -> &[u8];
    }

    // C++ types and signatures exposed to Rust.
    unsafe extern "C++" {
        include!("include/blobstore.h");

        type BlobstoreClient;

        fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
        fn put(self: Pin<&mut BlobstoreClient>, parts: &mut MultiBuf) -> u64;
        fn tag(self: Pin<&mut BlobstoreClient>, blobid: u64, tag: &str);
        fn metadata(&self, blobid: u64) -> BlobMetadata;
    }
}

这个例子浓缩了 Bridge 模块的几乎全部语法要素,逐行解读:

  • #[cxx::bridge(namespace = "org::blobstore")]:为 C++ 侧生成的代码指定命名空间,避免符号冲突。生成的 C++ 类、函数会落入 org::blobstore 命名空间。
  • mod ffi:桥接逻辑统一放在名为 ffi 的模块内,这是课程的约定俗成——bridge.md 明确指出 "The bridge is generally declared in an ffi module within your crate"。
  • 共享结构体 BlobMetadata:字段对两种语言都可见,usizeVec<String> 会被分别映射为两侧对应的类型。
  • extern "Rust" 区块:声明 Rust 侧对外暴露的类型与函数,C++ 可以调用它们;
  • unsafe extern "C++" 区块:声明 C++ 侧的类型与函数,Rust 可以调用它们。

桥接的 Rust 侧实现

extern "Rust" 中声明的 MultiBufnext_chunk 并非凭空定义,它们必须真实存在于 ffi 模块的父级作用域中。在 main.rs 里可以看到对应实现:

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)
}

MultiBuf 是“非连续文件对象上的连续块迭代器”——课程注释里说明,教学实现用 Vec<Vec<u8>> 简化,真实场景可能遍历 rope 之类的复杂数据结构或从某处惰性加载 chunk。

桥接的 C++ 侧实现

extern "C++" 中通过 include!("include/blobstore.h") 指定对应的 C++ 头文件,实际文件为 third_party/cxx/blobstore/include/blobstore.h。其中的 BlobstoreClient 是货真价实的 C++ 类:

namespace org {
namespace blobstore {

class BlobstoreClient {
public:
  BlobstoreClient();
  uint64_t put(MultiBuf &buf);
  void tag(uint64_t blobid, rust::Str tag);
  BlobMetadata metadata(uint64_t blobid) const;
  // ...
};

std::unique_ptr<BlobstoreClient> new_blobstore_client();

} // namespace blobstore
} // namespace org

桥接声明中的 fn put(self: Pin<&mut BlobstoreClient>, ...) 对应 C++ 的成员函数 putfn metadata(&self, ...) 对应 const 成员函数。可见 Bridge 声明与真实头文件签名一一对应,这正是 CXX 能完成静态校验的前提。

Rust 侧如何调用 C++ 实现

main 函数中,Rust 代码像调用本地函数一样调用桥接出来的 C++ 能力:

fn main() {
    let mut 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.pin_mut().put(&mut buf);
    println!("blobid = {}", blobid);

    // Add a tag.
    client.pin_mut().tag(blobid, "rust");

    // Read back the tags.
    let metadata = client.metadata(blobid);
    println!("tags = {:?}", metadata.tags);
}

注意 client.pin_mut():因为 putself: Pin<&mut BlobstoreClient> 声明,Rust 侧需要先取得可变 Pin 引用才能调用。

三、三种区块:Bridge 模块的内部结构

从 blobstore 示例可以看到,一个完整的 Bridge 模块由三部分组成,课程将后两部分拆成独立小节讲解:

1. 共享类型(Shared Types)

直接写在 mod ffi 内部的 struct 与 enum,C++ 与 Rust 两侧都能看见其完整布局。课程在 shared-types.md 中的示例:

#[cxx::bridge]
mod ffi {
    #[derive(Clone, Debug, Hash)]
    struct PlayingCard {
        suit: Suit,
        value: u8,  // A=1, J=11, Q=12, K=13
    }

    enum Suit {
        Clubs,
        Diamonds,
        Hearts,
        Spades,
    }
}

课程的注意事项值得牢记:

  • 只支持 C 风格(无字段的单元)枚举,带字段的数据枚举不能作为共享类型;
  • #[derive()] 支持有限,且派生能力会同步移植到 C++ 侧——例如 Rust 侧 derive(Hash) 后,CXX 也会为对应的 C++ 类型生成 std::hash 的实现。

2. extern "Rust":把 Rust 暴露给 C++

课程在 rust-bridge.md 中给出精简示例:

#[cxx::bridge]
mod ffi {
    extern "Rust" {
        type MyType; // Opaque type
        fn foo(&self); // Method on `MyType`
        fn bar() -> Box<MyType>; // Free function
    }
}

struct MyType(i32);

impl MyType {
    fn foo(&self) {
        println!("{}", self.0);
    }
}

fn bar() -> Box<MyType> {
    Box::new(MyType(123))
}

两个机制细节:

  • extern "Rust" 中列出的条目引用的是父模块作用域内的真实条目,上面 MyTypefoobar 就是在模块外定义的;
  • CXX 代码生成器会依据你的 extern "Rust" 区块生成一个 C++ 头文件,其中包含对应的 C++ 声明。该头文件的路径与包含 bridge 的 Rust 源文件路径相同,只是扩展名换成 .rs.h。例如源文件是 src/main.rs,生成的头文件就是 src/main.rs.h

3. extern "C++":把 C++ 暴露给 Rust

课程在 cpp-bridge.md 中说明,extern "C++" 区块(blobstore 中写作 unsafe extern "C++")会被展开为大致如下的 Rust 代码:

#[repr(C)]
pub struct BlobstoreClient {
    _private: ::cxx::private::Opaque,
}

pub fn new_blobstore_client() -> ::cxx::UniquePtr<BlobstoreClient> {
    extern "C" {
        #[link_name = "org$blobstore$cxxbridge1$new_blobstore_client"]
        fn __new_blobstore_client() -> *mut BlobstoreClient;
    }
    unsafe { ::cxx::UniquePtr::from_raw(__new_blobstore_client()) }
}

impl BlobstoreClient {
    pub fn put(&self, parts: &mut MultiBuf) -> u64 {
        extern "C" {
            #[link_name = "org$blobstore$cxxbridge1$BlobstoreClient$put"]
            fn __put(
                _: &BlobstoreClient,
                parts: *mut ::cxx::core::ffi::c_void,
            ) -> u64;
        }
        unsafe {
            __put(self, parts as *mut MultiBuf as *mut ::cxx::core::ffi::c_void)
        }
    }
}

从这段展开代码可以观察到 CXX 的两个核心设计:

  • Rust 侧的不透明类型 BlobstoreClient 是一个 #[repr(C)] 结构体,内部只有 _private: ::cxx::private::Opaque,Rust 无法直接访问其字段——所有权与内存布局完全由 C++ 侧控制;
  • 底层仍然走 extern "C" FFI,但符号名(link_name)由 CXX 统一编排(如 org$blobstore$cxxbridge1$...),并借助 UniquePtrPin 等封装隐藏了裸指针操作。

课程强调两个要点:

  • 程序员不需要“保证”自己写的签名准确无误:CXX 会执行静态断言,严格校验你声明的签名与 C++ 头文件中实际声明完全一致,不一致直接编译失败;
  • unsafe extern 块允许声明从 Rust 调用是安全的 C++ 函数:把“这个 C++ 函数可以安全调用”这一承诺交给声明者,调用点无需再写 unsafe

四、生成代码:Rust 侧与 C++ 侧分别长什么样

生成的 C++ 代码

课程在 generated-cpp.md 中展示了 extern "Rust" 区块生成出的 C++ 声明(源文件是 blobstore 的 bridge):

struct MultiBuf final : public ::rust::Opaque {
  ~MultiBuf() = delete;

private:
  friend ::rust::layout;
  struct layout {
    static ::std::size_t size() noexcept;
    static ::std::size_t align() noexcept;
  };
};

::rust::Slice<::std::uint8_t const> next_chunk(::org::blobstore::MultiBuf &buf) noexcept;

关键信息:

  • Rust 的不透明类型 MultiBuf 在 C++ 侧是一个继承 ::rust::Opaquefinal 结构体,析构函数被 delete,因为其生命周期由 Rust 管理;
  • 函数签名被翻译成 C++ 原生风格:&mut MultiBuf 变成引用,&[u8] 变成 ::rust::Slice<::std::uint8_t const>,且带 noexcept
  • 注意生成的代码同样被放入 org::blobstore 命名空间,印证了 namespace 属性的作用。

到哪里看生成代码

课程 bridge.md 给出了两种查看生成代码的方法:

  • Rust 侧:使用 cargo-expand 展开过程宏。大多数示例直接运行 cargo expand ::ffi 只展开 ffi 模块即可(课程特别注明:这个方法不适用于 Android 项目,因为 Android 项目通常不在本机直接构建 Rust crate);
  • C++ 侧:查看构建产物目录 target/cxxbridge,里面就是生成的 .rs.h / .rs.cc 文件。

五、跨语言类型映射:哪些类型可以穿越大桥

并不是所有类型都能在两侧直接传递,CXX 维护着一张白名单。课程在 type-mapping.md 中给出完整映射表:

Rust 类型 C++ 类型
String rust::String
&str rust::Str
CxxString std::string
&[T] / &mut [T] rust::Slice
Box<T> rust::Box<T>
UniquePtr<T> std::unique_ptr<T>
Vec<T> rust::Vec<T>
CxxVector<T> std::vector<T>

这些类型可以用在共享结构体的字段里,也可以用作 extern 函数的参数与返回值。

课程特别解释了为什么 Rust 的 String 不直接映射到 std::string

  1. UTF-8 不变量std::string 是任意字节序列,不维护 String 要求的 UTF-8 合法性约束;
  2. 内存布局不同:两种类型在内存中的布局不一致,无法直接在两种语言间原样传递;
  3. 移动语义不匹配std::string 依赖移动构造函数,这与 Rust 的 move 语义不同,因此 std::string 不能按值传给 Rust。

所以 CXX 为每种语言各维护了一套“镜像类型”(rust::Stringrust::Strrust::Vec 等),由 rust/cxx.h 提供(见 blobstore.h 顶部的 #include "rust/cxx.h")。

共享枚举的特殊实现

课程在 shared-enums.md 中揭示了共享枚举在 Rust 侧的“惊人”真相——它生成的其实是一个包装数值的透明结构体,而不是真正的 Rust enum:

#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(transparent)]
pub struct Suit {
    pub repr: u8,
}

#[allow(non_upper_case_globals)]
impl Suit {
    pub const Clubs: Self = Suit { repr: 0 };
    pub const Diamonds: Self = Suit { repr: 1 };
    pub const Hearts: Self = Suit { repr: 2 };
    pub const Spades: Self = Suit { repr: 3 };
}

原因在课程中说得非常清楚:在 C++ 中,enum class 持有不在列举范围内的值并不构成未定义行为(UB),为了让 Rust 表示与 C++ 行为保持一致,Rust 侧就不能使用会假设值域闭合的真枚举,而必须退化为可容纳任意 u8 的透明包装类型。

六、在 Android 构建系统中落地:两条 genrule

Bridge 模块最终要融入 Android 构建体系。课程 android-cpp-genrules.md 展示了标准做法——用两条 genrule 分别生成 CXX 头文件与 CXX 源码,再作为 cc_library_static 的输入:

// Generate a C++ header containing the C++ bindings
// to the Rust exported functions in lib.rs.
genrule {
    name: "libcxx_test_bridge_header",
    tools: ["cxxbridge"],
    cmd: "$(location cxxbridge) $(in) --header > $(out)",
    srcs: ["lib.rs"],
    out: ["lib.rs.h"],
}

// Generate the C++ code that Rust calls into.
genrule {
    name: "libcxx_test_bridge_code",
    tools: ["cxxbridge"],
    cmd: "$(location cxxbridge) $(in) > $(out)",
    srcs: ["lib.rs"],
    out: ["lib.rs.cc"],
}

要点:

  • cxxbridge 是独立的命令行工具,专门负责生成 bridge 模块的 C++ 侧代码,它已随 Android 内置、可作为 Soong tool 直接使用;
  • 命名约定:如果 Rust 源文件是 lib.rs,那么生成的头文件叫 lib.rs.h、源文件叫 lib.rs.cc(不过这只是约定,并非强制);
  • 第一条 genrule 用 --header 标志生成 C++ 头文件,第二条不带标志生成 C++ 实现源文件,两者作为输入喂给 cc_library_static,最终与 Rust 侧产物链接成一个完整的混合库。

七、综合理解:CXX 桥接的完整工作流

将课程各章节串起来,一个 CXX 桥接的完整工作流是:

  1. 在 crate 内的 ffi 模块上标注 #[cxx::bridge(namespace = "...")](对应 bridge.md);
  2. 在模块内按需声明共享 struct/enum、extern "Rust"extern "C++" 三种区块,写清跨语言签名(对应 rust-bridge.mdcpp-bridge.md);
  3. 在父模块中实现 extern "Rust" 声明的类型与函数;在 C++ 头文件中实现 extern "C++" 声明的类型与函数(参考 blobstore.h);
  4. CXX 通过过程宏在编译期生成两侧绑定代码,并用静态断言校验签名一致性,任何不匹配都会在编译期暴露(对应 generated-cpp.md);
  5. cargo expand ::ffi 检查生成的 Rust 代码、到 target/cxxbridge 检查生成的 C++ 代码;在 Android 中用两条 cxxbridge genrule 接入 cc_library_static(对应 android-cpp-genrules.md)。

借助这套机制,Rust 与 C++ 的互操作从“手写 extern "C" + 裸指针 + 手工对齐布局”升级为“声明式桥接 + 编译期校验”,这也是 CXX 被课程选中作为 Android Rust 互操作教学主线的根本原因。本仓库还提供了与 Bridge 模块配套的错误处理(Result 传播)、异常映射等进阶内容,可在 src/android/interoperability/cpp/ 目录下继续深入学习。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395