ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

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

comprehensive-rust 实战:在 Android Soong 构建系统中用 genrule + cxxbridge 生成 CXX 互操作绑定 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 之间的调用通过 FFIForeign 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_headerscc_library_static通过它引入 genrule 输出的头文件。这里同时列出了cxx-bridge-headerCXX 自身的运行时头文件模块与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]声明→ 两条 genrulecxxbridge生成头与源码→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 中落地时的实用提醒两条 genrule 必须成对出现。头文件与实现源码来自同一次桥接声明的两侧缺失任何一条都会导致链接期符号缺失或头文件声明找不到实现。--header标志不要漏。漏掉它$(out)里得到的将是 C 源码而非头文件generated_headers引入后编译必然报错。输出名与$(out)保持一致。out中的文件名要和cmd重定向的$(out)一一对应沿用lib.rs→lib.rs.h/lib.rs.cc的惯例最省心。generated_headers记得带上cxx-bridge-header。仓库示例表明仅引入业务 genrule 头文件是不够的CXX 的运行时头文件模块也必须加入否则::rust::前缀的类型定义无法解析。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、VecT ↔ rust::VecT、UniquePtrT ↔ std::unique_ptrT等。理解这张表有助于你在阅读 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.rsAndroid 互操作的入门语境见 InteroperabilityAndroid 章节总入口见 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),仅供参考
返回列表