首页
/ comprehensive-rust 实战:在 Android Soong 构建系统中用 genrule + cxxbridge 生成 CXX 互操作绑定

comprehensive-rust 实战:在 Android Soong 构建系统中用 genrule + cxxbridge 生成 CXX 互操作绑定

2026-09-09 10:58:39作者:宣聪麟

本篇技术指南以 comprehensive-rust 课程的 Android 章节为基础,聚焦于「在 Android 构建环境中(Soong 构建系统)将 Rust 与 C++ 通过 CXX 桥接」的关键一环:如何编写两个 genrule,分别生成 CXX 头文件(.rs.h)与 CXX 源码文件(.rs.cc),并让它们作为 cc_library_static 的输入参与构建。读完本文,你将掌握 cxxbridge 工具在 Android 构建系统中的用法、genrule 的完整配置写法、命名约定,以及如何把它接入真实的 Android.bp 模块(仓库内 third_party/cxx/blobstore 提供了可对照的完整示例)。

背景:为什么在 Android 中需要生成 CXX 绑定

在 comprehensive-rust 课程的 Android 互操作章节中,Rust 与 C++ 之间的调用通过 FFI(Foreign Function Interface)实现。而课程选用的核心工具是 CXX 生态:它通过一个 #[cxx::bridge] 属性宏声明桥接模块,自动为两侧生成匹配的类型与函数定义。

在常规的 Cargo 项目中,CXX 的生成过程由 cxx-build 这类构建脚本驱动;而在 Android 平台上,构建系统是 Soong,.bp 文件描述的模块没有 Cargo 式的构建脚本。因此课程给出了 Android 专属的解法——用两条 genrule 显式调用独立的 cxxbridge 命令行工具,把「代码生成」这一步变成构建图中的两个规则节点。这就是关联文档 android-cpp-genrules.md 的核心内容。

桥接模块本身的声明方式,参见 The Bridge Module:你需要在 Rust 源码里用 #[cxx::bridge] 标注一个模块(通常叫 ffi),其中通过 extern "Rust" 块暴露 Rust 侧类型与函数给 C++(见 Rust Bridge Declarations),通过 unsafe extern "C++" 块声明 C++ 侧类型与函数给 Rust 使用(见 C++ Bridge Declarations)。而 genrule 的作用,就是把这份声明翻译成真正可编译的 C++ 代码。

两条 genrule 的完整写法

在 Android Soong 的 Android.bp 文件中,你需要为同一个 Rust 源文件声明两条 genrule,一条生成头文件、一条生成源码文件。原文档给出的模板如下:

// 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"],
}

逐项拆解这两条规则:

属性 第一条 genrule 第二条 genrule 说明
name libcxx_test_bridge_header libcxx_test_bridge_code Soong 模块名,需在项目内唯一;后续被 generated_headers / generated_sources 引用
tools ["cxxbridge"] ["cxxbridge"] 声明本规则依赖名为 cxxbridge 的工具模块,构建时该工具二进制会被提供到执行环境
cmd $(location cxxbridge) $(in) --header > $(out) $(location cxxbridge) $(in) > $(out) 唯一的差别是第一条多了 --header 标志,用于输出头文件
srcs ["lib.rs"] ["lib.rs"] 输入是包含 #[cxx::bridge] 的 Rust 源文件
out ["lib.rs.h"] ["lib.rs.cc"] 输出文件名,遵循下述命名约定

这里的关键点在于两条规则之间的差异只有 --header 这一个命令行标志:

  • cxxbridge lib.rs --header 输出 C++ 头文件,其中包含暴露给 C++ 的 Rust 函数/类型声明(对应 extern "Rust" 段);
  • cxxbridge lib.rs 输出 C++ 实现源码,即 Rust 侧调用 C++ 函数时需要链接进去的胶水代码(对应 unsafe extern "C++" 段)。

关于生成物内容的具体形态,可对照课程 Generated C++ 一节:头文件里会生成继承自 ::rust::Opaque 的不透明类型、以及 ::rust::Slice<...> 等桥接签名的 C++ 声明。

关于 cxxbridge 工具与命名约定

原文档在 <details> 中补充了两个重要的实践知识,写作配置时务必留意:

其一,cxxbridge 是独立工具。 它是一个独立的命令行工具,专门用于生成桥接模块的 C++ 侧代码,并且已经随 Android 系统内置、可作为 Soong 的 tool 使用。正因为如此,你不需要像 Cargo 项目那样引入 cxx-build 构建脚本,只需在 tools: ["cxxbridge"] 中声明依赖即可。这条 genrule 会以 $(in)(即 srcs 中的 lib.rs)为输入,把 stdout 重定向到 $(out)

其二,命名约定并非强制。 按惯例,如果你的 Rust 源文件叫 lib.rs,那么生成的头文件应命名为 lib.rs.h、源码文件应命名为 lib.rs.cc。这个约定便于后续被 cc_library_staticgenerated_headers / generated_sources 引用时直观对应,但它不是被强制执行的规则——out 字段里的名字你可以自行指定(比如 my_header.rs.h),只要 cmd 中的重定向输出与 out 一致即可。

另外注意:既然生成的 C++ 声明与 #[cxx::bridge] 中的签名严格对应,如果修改了桥接模块,就必须重新生成并重新编译依赖它的 C++ 库;构建系统通过 genrule 的输入输出依赖关系自动保证这一点,只要 srcs 里写对了 Rust 源文件。

让生成物进入 cc_library_static:真实仓库示例

原文档指出,这两条 genrule 的输出随后会「作为 cc_library_static 的输入」。这一步的完整接线方式,仓库内的真实例子位于 third_party/cxx/blobstore/Android.bp,它演示了一个同时包含 Rust 二进制与 C++ 静态库的完整模块,摘录如下:

cc_library_static {
    name: "blobstore_cpp",
    srcs: ["src/blobstore.cc"],
    generated_headers: [
        "cxx-bridge-header",
        "blobstore_bridge_header"
    ],
    generated_sources: ["blobstore_bridge_code"],
}

genrule {
    name: "blobstore_bridge_header",
    tools: ["cxxbridge"],
    cmd: "$(location cxxbridge) $(in) --header > $(out)",
    srcs: ["src/main.rs"],
    out: ["main.rs.h"],
}

genrule {
    name: "blobstore_bridge_code",
    tools: ["cxxbridge"],
    cmd: "$(location cxxbridge) $(in) > $(out)",
    srcs: ["src/main.rs"],
    out: ["main.rs.cc"],
}

rust_binary {
    name: "blobstore",
    srcs: ["src/main.rs"],
    rustlibs: ["libcxx"],
    static_libs: ["blobstore_cpp"],
}

对照原文档模板,可以看到在实际项目中的对应关系:

  • generated_headerscc_library_static 通过它引入 genrule 输出的头文件。这里同时列出了 cxx-bridge-header(CXX 自身的运行时头文件模块)与 blobstore_bridge_header(我们自己的 genrule 产物 main.rs.h),两者缺一不可——前者提供 rust::Stringrust::Slice 等运行时类型定义,后者提供桥接声明。
  • generated_sources:引入 genrule 输出的实现源码 main.rs.cc,它会被编译进静态库,其中包含 Rust 侧调用 C++ 时所需的符号。
  • rust_binary:最终的 Rust 可执行文件通过 static_libs: ["blobstore_cpp"] 链接上面的 C++ 静态库,并通过 rustlibs: ["libcxx"] 引入 CXX 的 Rust 运行时支持。

也就是说,一条完整的链路是:main.rs#[cxx::bridge] 声明)→ 两条 genrule(cxxbridge 生成头与源码)→ cc_library_static(编译 C++ 侧实现与生成的胶水代码)→ rust_binary(链接进 Rust 程序)。你可以在 third_party/cxx/blobstore/src/main.rs 中查看被这两条 genrule 作为输入的桥接模块本体——它声明了 org::blobstore 命名空间下的共享结构体、extern "Rust" 段(如 next_chunk 函数与 MultiBuf 类型)和 unsafe extern "C++" 段(如 new_blobstore_clientput 等方法)。

实操要点与易错项

结合文档与仓库源码,给出几条在 Android 中落地时的实用提醒:

  1. 两条 genrule 必须成对出现。头文件与实现源码来自同一次桥接声明的两侧,缺失任何一条都会导致链接期符号缺失或头文件声明找不到实现。
  2. --header 标志不要漏。漏掉它,$(out) 里得到的将是 C++ 源码而非头文件,generated_headers 引入后编译必然报错。
  3. 输出名与 $(out) 保持一致out 中的文件名要和 cmd 重定向的 $(out) 一一对应;沿用 lib.rslib.rs.h / lib.rs.cc 的惯例最省心。
  4. generated_headers 记得带上 cxx-bridge-header。仓库示例表明,仅引入业务 genrule 头文件是不够的,CXX 的运行时头文件模块也必须加入,否则 ::rust:: 前缀的类型定义无法解析。
  5. Android 项目里无法用 cargo-expand 预览展开结果。桥接模块的生成 Rust 代码在常规 Cargo 项目中可以用 cargo expand ::ffi 查看(见 The Bridge Module),但这一点不适用于 Android 工程;此时生成的 C++ 代码就是观察桥接结果的主要窗口。

与 CXX 类型映射和错误处理的关系

生成的 C++ 绑定中出现的类型并非随意的:课程 Additional Types 一节给出了 Rust 与 C++ 类型的对应表,例如 String ↔ rust::String&str ↔ rust::StrVec<T> ↔ rust::Vec<T>UniquePtr<T> ↔ std::unique_ptr<T> 等。理解这张表有助于你在阅读 genrule 生成的头文件时判断签名是否正确——例如 Rust 的 String 并不直接映射到 std::string,两者内存布局与不变量都不一致,因此桥接层需要专门类型转换。

同时,如果桥接声明中使用了返回 Result 的 C++ 函数,生成的胶水代码会负责在 C++ 侧捕获异常并转换为 Err 返回给 Rust;对未声明返回 Resultextern "C++" 函数,一旦抛出异常会触发 std::terminate(等价于异常穿过 noexcept 函数),详见 C++ Error Handling。这些语义由 cxxbridge 生成的代码承担,genrule 只需保证生成物被正确编译进 cc_library_static 即可。

延伸阅读

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
899
5.83 K
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
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
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
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
525