windows-bindgen:从 Windows 元数据生成 Rust 绑定的完整实战指南
【免费下载链接】windows-rsRust for Windows项目地址: https://gitcode.com/GitHub_Trending/wi/windows-rs
windows-bindgen是 windows-rs 项目(Rust for Windows)中的代码生成器,负责把 Windows 元数据(.winmd文件)转换成可供 Rust 程序直接调用的 FFI 绑定代码。它支撑着windows、windows-sys以及仓库内众多windows-*crate 的生成管线,是理解整个 windows-rs 体系如何从「元数据」走向「可调用 API」的关键一环。读完本文,你将掌握如何在build.rs中配置并调用windows-bindgen、如何用过滤规则裁剪 API 范围、三种输出布局与三种代码风格的区别,以及变参函数与参数方向投影的底层规则。
windows-bindgen 是什么
windows-bindgen是一个从 Windows 元数据生成 Rust 绑定的 crate,其定位与职责在 crates/libs/bindgen/Cargo.toml 中描述为 "Code generator for Windows metadata"。它读取 WinRT 与 Win32 的.winmd元数据文件,解析其中的类型、方法、参数方向与调用约定信息,再结合本地的 Rust 投影策略,产出风格各异的 Rust 绑定源码。
从源码结构看,它的实现被组织为多个职责清晰的模块:
- crates/libs/bindgen/src/cli.rs:命令行风格的参数解析与
bindgen(...)入口; - crates/libs/bindgen/src/lib.rs:
Bindgen构建器、布局/风格枚举与整体生成流程; - crates/libs/bindgen/src/config.rs:贯穿生成过程的
Config上下文; - crates/libs/bindgen/src/types/:各类元数据类型的代码写出逻辑(class、interface、struct、enum、method、cpp_fn 等);
- crates/libs/bindgen/src/winmd/:
.winmd文件的读取与解析。
快速开始:在 build.rs 中生成绑定
添加依赖
windows-bindgen以「构建期依赖」的形式参与编译:生成器本身只需要在build.rs阶段运行,而生成出来的绑定代码在运行时依赖windows-link(链接声明)与windows-core(类型与 trait 基础设施)。以仓库 readme 推荐的 0.100 版本为例,在Cargo.toml中这样配置:
[dependencies.windows-link] version = "0.100" [build-dependencies.windows-bindgen] version = "0.100"编写 build.rs
在项目根目录创建build.rs,调用windows_bindgen::bindgen(...)并传入一组命令行风格的参数:
let args = [ "--out", "src/bindings.rs", "--flat", "--sys", "--filter", "GetTickCount", ]; windows_bindgen::bindgen(args);这段代码会读取默认的 Windows 元数据(WinRT + Win32 两份内置元数据,见 crates/libs/bindgen/src/lib.rs 中default_reader/default_input的实现),按过滤规则挑选 API,并把生成的绑定写入src/bindings.rs。生成完成后,在业务代码里按普通 Rust 模块使用即可:
mod bindings; unsafe { println!("{}", bindings::GetTickCount()); }注意GetTickCount是 Win32 API,属于unsafe的外部函数调用,因此调用被包裹在unsafe块中。如果你的过滤目标换成 WinRT 方法,调用形式会类似bindings::Windows::...命名空间路径(未使用--flat时)。
两种调用方式:bindgen命令式 API 与Bindgen构建器
命令式 API:windows_bindgen::bindgen
bindgen函数接受一个字符串迭代器,参数与命令行开关一一对应(完整清单见 crates/libs/bindgen/src/cli.rs):
| 参数 | 作用 |
|---|---|
--in | 指定元数据文件或目录(.winmd),可用default表示内置默认元数据 |
--out | 生成的 Rust 文件路径,恰好必须出现一次 |
--filter | 包含或排除(加!前缀)API 的过滤规则 |
--rustfmt | 覆盖默认的 Rust 格式化器路径 |
--derive | 为生成的类型附加额外派生 trait |
--flat | 省略命名空间模块,输出扁平条目列表 |
--package | 按命名空间拆分为多个文件并生成 Cargo features |
--sys | 生成仅依赖windows-link的原始绑定 |
--extern | 与--sys组合,改用extern声明而非link!宏 |
--minimal | 省略类包装、继承转发器与句柄包装 |
--implement | 为选中的 WinRT 接口生成实现 trait |
--compose | 选择 minimal 模式下的可组合 WinRT 类 |
--dead-code | 生成pub(crate)条目,用于死代码分析 |
--etc | 从命令文件中读取其余参数 |
--filter-file | 从文本文件读取过滤规则 |
构建器 API:Bindgen
bindgen的参数解析本质上是在构造一个Bindgen构建器(见 crates/libs/bindgen/src/cli.rs)。如果你更偏好类型安全的流式写法,可以直接使用构建器。仓库 readme 与 crates/libs/bindgen/src/lib.rs 给出的等价示例:
windows_bindgen::Bindgen::new() .output("src/bindings.rs") .filter("GetTickCount") .write();Bindgen的完整方法面覆盖了命令式 API 的全部能力:输入侧有input(添加.winmd文件)、input_default(添加内置默认元数据)、input_bytes/input_byte_sets(从内存字节构造元数据)、inputs(批量添加);输出侧有output、flat、package、sys、minimal、extern_fns、derive/derives、implement/implement_all、compose、rustfmt、dead_code等(见 crates/libs/bindgen/src/lib.rs)。
值得注意的约束(部分会在调用期直接 panic 提示):
--flat与--package互斥;--sys与--minimal互斥;--extern仅在与--sys组合时有效;--compose仅在与--minimal组合时有效;--implement的「全选」与「定向选择」两种形态互斥;- 必须有至少一条
--filter(见 crates/libs/bindgen/src/lib.rs 的断言)。
用--etc与过滤文件组织复杂参数
当过滤规则很多时,把参数写进命令行数组会让build.rs变得臃肿。readme 推荐改用--etc从文本文件读取全部参数:
windows_bindgen::bindgen(["--etc", "bindings.txt"]);命令文件是纯文本,支持以//开头的注释行,且解析器会保留{...}花括号组不被空白拆散(见 crates/libs/bindgen/src/cli.rs 的read_tokens)。仓库自身就是一个大规模使用该机制的范例:在 crates/tools/bindings/src/main.rs 中,每个windows-*crate 的绑定都由一条bindgen(["--etc", "crates/tools/bindings/src/xxx.txt"])生成。例如 crates/tools/bindings/src/result.txt 的真实内容:
--out crates/libs/result/src/bindings.rs --flat --sys --filter E_UNEXPECTED ERROR_INVALID_DATA ... FormatMessageW GetLastError SysFreeString SysStringLen可以看到「输出路径 + 布局/风格开关 + 过滤清单」的组织方式。若只想把过滤规则单独抽成文件,则使用Bindgen::filter_file/filter_files构建器方法或--filter-file命令行开关(见 crates/libs/bindgen/src/lib.rs)。
过滤规则详解:从命名空间到方法级裁剪
--filter决定哪些 API 进入绑定。规则支持从粗到细的多个粒度(详见 crates/libs/bindgen/src/lib.rs 与 crates/libs/bindgen/src/cli.rs):
包含与排除
- 直接给出函数或类型名即可包含;
- 前缀加
!表示排除; - 过滤规则可以是:函数名、类型名、命名空间前缀、完全限定名、或
Namespace.Type::Member形式的方法级条目。
示例(取自 cli.rs 文档):
--filter Windows.Win32.Storage.FileSystem.GetFullPathNameW --filter Windows.Win32.Storage.FileSystem.GetFullPathNameW !Windows.Win32.Storage.FileSystem.WIN32_FIND_DATAW --filter Windows.Win32.Storage.FileSystem --filter Windows.Win32.Storage.FileSystem !Windows.Win32.Storage.FileSystem.WIN32_FIND_DATAW前两条是精确 API 过滤(含排除某类型的补充规则),后两条是命名空间前缀过滤(同样可以再排除其中个别类型)。执行时,带!的规则会被拆入独立的 exclude 列表,未带!的进入 include 列表(见 crates/libs/bindgen/src/lib.rs)。
方法级过滤与成员语法
过滤可以精确到方法、属性、事件,语法为Namespace.Type::Member:
--filter Windows.UI.Xaml.Controls.Button Windows.UI.Xaml.Controls.TextBlock::put_Text Windows.UI.Xaml.Controls.TextBlock::Property.FontSize Windows.UI.Xaml.UIElement::Event.PointerPressed- 裸类型名(
Button)投影出完整类型; Type::{}只生成一个空壳(仅类型名);Type::Member与Type::{a, b}选择单个或多个成员;Property.Name与Event.Name分别选择属性与事件的访问器对。
类型选择算法:closure 与 map
不同类型的过滤规则走不同的类型选择管线(见 crates/libs/bindgen/src/lib.rs):
- 精确过滤(无宽泛命名空间规则、非 package 布局)使用
TypeClosure,做自底向上的类型闭包收集,只带回依赖的类型; - 宽泛过滤(命名空间前缀)与 package 布局使用
TypeMap::filter,做自顶向下的整树扫描。
这一设计让「精确抓取少量 API」与「按命名空间抓取大批 API」两种场景各得其所。
三种输出布局与三种代码风格
windows-bindgen在「怎么排布」与「写成什么样」两个维度上分别提供了选项。
布局(Layout):Modules / Flat / Package
Layout枚举定义在 crates/libs/bindgen/src/lib.rs:
| 布局 | 说明 |
|---|---|
Modules(默认) | 每个元数据命名空间生成一个 Rust 模块 |
Flat(--flat) | 扁平条目列表,无命名空间模块 |
Package(--package) | 每个命名空间一个文件,并附带按命名空间派生的 Cargo features |
Package 布局还会把Windows.Win32.*私有头文件命名空间折叠到公共伞形命名空间Windows.Win32(见flat_module_namespace,crates/libs/bindgen/src/lib.rs),并为每个命名空间派生 feature 名(namespace_feature,同上 crates/libs/bindgen/src/lib.rs),例如Windows.Win32.Storage.FileSystem对应Storage_FileSystem这样的 feature。模块化布局的递归写出逻辑在 crates/libs/bindgen/src/config.rs。
风格(Style):Default / Sys / Minimal
Style枚举定义在 crates/libs/bindgen/src/lib.rs:
- Default(默认):高保真绑定,带类包装、trait 实现、句柄人体工学等完整投影,依赖
windows-core; - Sys(
--sys):原始风格绑定,仅依赖windows-link,省略标准 trait 派生与windows-coretrait;可选extern_fns以extern { fn ... }替代link!宏(见 crates/libs/bindgen/src/lib.rs); - Minimal(
--minimal):最小绑定,去掉类包装方法、继承转发器、句柄人体工学与自由函数包装,字符串输入直接暴露为&str、返回为String(见 crates/libs/bindgen/src/lib.rs)。
windows与windows-sys两大发布 crate 正是这两种风格的典型代表:前者是 Default/Modules 风格,后者是 Sys/Flat 风格。--dead-code配合使用会把生成条目标记为pub(crate),让未被使用的绑定以死代码警告的形式暴露出来(见 crates/libs/bindgen/src/config.rs)。
变参函数的处理规则
Windows 原生 API 中存在少量 C 变参(variadic)函数(如wsprintfW)。readme 明确指出这类函数的投影策略:变参原生导出仅在--sys风格下发出,且生成的声明保留字面的...尾部;默认与 minimal 绑定会省略它们,而不是暴露一个定长前缀包装(fixed-prefix wrapper)。
这一点在源码中体现为双重防护(见 crates/libs/bindgen/src/types/cpp_fn.rs):
- 若签名带
VARARG标志且当前风格不是--sys,直接拒绝并给出错误信息 "cannot be projected by rich or minimal bindings; use--sysfor its raw declaration"; - 即使处于
--sys模式,若该函数使用的调用约定无法被 stable Rust 表示为 C 变参(variadic_abi返回None),同样拒绝生成。
换句话说:变参 API 只以「原始声明 + 字面...」的形式存在于 sys 风格绑定中,调用方需要自行承担传参责任;rich/minimal 风格宁可缺失也不生成不安全的定长包装。
参数方向投影策略:raw facts 与本地策略的分工
readme 用一整段篇幅说明了参数方向的投影规则,核心思想是:参数方向的基础事实(raw facts)来自windows-metadata,但 Rust 投影策略(policy)由本 crate 自行决定。二者分工:元数据负责「事实是什么」,bindgen 负责「Rust 里怎么表示」。
具体规则如下:
Input与Unspecified方向:走纯输入分支,投影为输入类型(只读借用或值);InputOutput方向:保持可变切片形状(mutable slice shapes),调用方可读写;- 仅标记
Output的参数:保持原始指针/计数参数形状,这样调用方可以提供未初始化存储(uninitialized storage),由被调函数填充; - 必需的输入缓冲区:其长度既可以是无符号切片长度,也可以是有符号切片长度(signed or unsigned slice lengths);
- 带符号计数的可选缓冲区:保持显式(不折叠为切片),因为它们可能使用负数哨兵值(negative sentinel values,如
-1表示「无」); - 尾部 retval(按值返回的结果参数):必须满足「仅输出、必需、非保留(non-reserved)、非计数(uncounted)、指针形状」五个条件才会被识别;
- void 指针目标与大小上限(>16 字节)的限制:只适用于「未显式标记」的启发式候选参数,显式标注的 retval 不受此限(见 crates/libs/bindgen/src/param.rs 中
is_retval_candidate与 size 检查的注释)。
retval 的判定在 crates/libs/bindgen/src/signature.rs 中分为两条路径:先检查最后一个参数是否带显式 retval 属性;若没有,再对尾部指针形参做启发式判定。也就是说,「显式标记」优先于「启发式猜测」,启发式仅在未标记时兜底,从而避免把大结构指针误判为按值返回。
生成流程中的依赖解析与隐式引用
非 sys 风格的绑定生成时,windows-bindgen会扫描元数据中是否出现特定的 WinRT 基础类型,并隐式注册对同仓库兄弟 crate 的引用(见 crates/libs/bindgen/src/lib.rs)。映射关系如下:
| 元数据中出现 | 引用的 crate |
|---|---|
Windows.Foundation的 Async/异步相关类型 | windows-future |
Windows.Foundation.Collections的集合接口与委托 | windows-collections |
Windows.Foundation.IReference | windows-reference |
Windows.Foundation.DateTime/TimeSpan | windows-time |
Windows.Foundation.Numerics的向量/矩阵类型 | windows-numerics |
这样,生成出来的高保真绑定可以直接复用这些 crate 中已有的 Rust 类型与 trait,而无需重复生成。sys 风格不引入这类引用(仅依赖windows-link),这也是 sys 绑定更「自包含」的原因。
诊断与性能:WINDOWS_BINDGEN_TIMINGS
生成过程本身也被打上了计时探针:只要设置环境变量WINDOWS_BINDGEN_TIMINGS,就会在 stderr 输出各阶段耗时(metadata 读取、references 解析、selection 类型选择、planning 规划、render 渲染、format 格式化、write 写出、total 总计),见 crates/libs/bindgen/src/lib.rs 的report_timing。排查大型元数据生成缓慢问题时,可以用它定位瓶颈阶段。
从元数据到绑定:一次生成的完整旅程
综合以上内容,一次windows_bindgen::bindgen(...)调用的完整执行链如下(对应 crates/libs/bindgen/src/lib.rs 中Bindgen::write的实现顺序):
- 参数校验:确认
--out非空、过滤规则非空、compose与minimal搭配等约束; - 元数据读取:加载内置默认元数据或
--in指定的.winmd(文件/目录/内存字节),目录输入只收集扩展名为winmd的文件(crates/libs/bindgen/src/lib.rs); - 引用解析:注册隐式兄弟 crate 引用(非 sys 风格);
- 类型选择:解析过滤条目,根据过滤粒度选择
TypeClosure(精确)或TypeMap(宽泛/package)管线,产出类型集合; - 派生与实现规划:计算额外 derive、
--implement的实现 trait、仅事件委托集合; - 写出:按布局(模块/扁平/包)渲染 token 流,调用 rustfmt 格式化,写入目标文件。
仓库的绑定生成器工具 crates/tools/bindings/src/main.rs 是这一管线的规模化应用:一次cargo run即可按--etc清单重新生成result、registry、strings、core、collections等多个 crate 的绑定,是理解「如何用--etc组织生成任务」的最佳现成范例。
总结
windows-bindgen把「Windows 元数据 → Rust 绑定」这条链路拆解为清晰可控的配置维度:依赖上以「build-dependency 生成 + windows-link/windows-core 运行时支撑」双轨配合;调用上支持命令式参数与流式构建器两种形态;裁剪上支持从函数、类型、命名空间到方法/属性/事件的多级过滤;产出上由布局(Modules/Flat/Package)与风格(Default/Sys/Minimal)两轴决定。而对变参函数、参数方向与 retval 的投影规则,则体现了「元数据提供事实、本 crate 决定策略」的分层设计哲学。掌握了这些维度,你就可以像仓库自身那样,把任何 Windows API 集合精确、可控地投影为项目所需的 Rust 绑定。
【免费下载链接】windows-rsRust for Windows项目地址: https://gitcode.com/GitHub_Trending/wi/windows-rs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考