☰
comprehensive-rust 实战:在 Android Soong 构建系统中用 genrule + cxxbridge 生成 CXX 互操作绑定
2026/9/25 15:20:55 网站建设 项目流程

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

【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust

本篇技术指南以 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说明
namelibcxx_test_bridge_headerlibcxx_test_bridge_codeSoong 模块名,需在项目内唯一;后续被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_static的generated_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_headers:cc_library_static通过它引入 genrule 输出的头文件。这里同时列出了cxx-bridge-header(CXX 自身的运行时头文件模块)与blobstore_bridge_header(我们自己的 genrule 产物main.rs.h),两者缺一不可——前者提供rust::String、rust::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_client、put等方法)。

实操要点与易错项

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

  1. 两条 genrule 必须成对出现。头文件与实现源码来自同一次桥接声明的两侧,缺失任何一条都会导致链接期符号缺失或头文件声明找不到实现。
  2. --header标志不要漏。漏掉它,$(out)里得到的将是 C++ 源码而非头文件,generated_headers引入后编译必然报错。
  3. 输出名与$(out)保持一致。out中的文件名要和cmd重定向的$(out)一一对应;沿用lib.rs→lib.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::Str、Vec<T> ↔ rust::Vec<T>、UniquePtr<T> ↔ std::unique_ptr<T>等。理解这张表有助于你在阅读 genrule 生成的头文件时判断签名是否正确——例如 Rust 的String并不直接映射到std::string,两者内存布局与不变量都不一致,因此桥接层需要专门类型转换。

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

延伸阅读

  • Building in Android(本文所依据的原始文档)
  • Android C++ 互操作总览 所在章节:课程将「构建 Android 工程」作为 C++ 互操作链路的一环,与 bridge 声明、生成的 C++ 等内容串联讲解
  • 可对照的完整工程:third_party/cxx/blobstore/Android.bp 与 third_party/cxx/blobstore/src/main.rs
  • Android 互操作的入门语境见 Interoperability,Android 章节总入口见 android.md

【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询