windows-bindgen:从 Windows 元数据生成 Rust 绑定的完整实战指南
2026/9/15 16:36:55 网站建设 项目流程

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 绑定代码。它支撑着windowswindows-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(批量添加);输出侧有outputflatpackagesysminimalextern_fnsderive/derivesimplement/implement_allcomposerustfmtdead_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::MemberType::{a, b}选择单个或多个成员;
  • Property.NameEvent.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_fnsextern { fn ... }替代link!宏(见 crates/libs/bindgen/src/lib.rs);
  • Minimal--minimal):最小绑定,去掉类包装方法、继承转发器、句柄人体工学与自由函数包装,字符串输入直接暴露为&str、返回为String(见 crates/libs/bindgen/src/lib.rs)。

windowswindows-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):

  1. 若签名带VARARG标志且当前风格不是--sys,直接拒绝并给出错误信息 "cannot be projected by rich or minimal bindings; use--sysfor its raw declaration";
  2. 即使处于--sys模式,若该函数使用的调用约定无法被 stable Rust 表示为 C 变参(variadic_abi返回None),同样拒绝生成。

换句话说:变参 API 只以「原始声明 + 字面...」的形式存在于 sys 风格绑定中,调用方需要自行承担传参责任;rich/minimal 风格宁可缺失也不生成不安全的定长包装。

参数方向投影策略:raw facts 与本地策略的分工

readme 用一整段篇幅说明了参数方向的投影规则,核心思想是:参数方向的基础事实(raw facts)来自windows-metadata,但 Rust 投影策略(policy)由本 crate 自行决定。二者分工:元数据负责「事实是什么」,bindgen 负责「Rust 里怎么表示」。

具体规则如下:

  • InputUnspecified方向:走纯输入分支,投影为输入类型(只读借用或值);
  • 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.IReferencewindows-reference
Windows.Foundation.DateTime/TimeSpanwindows-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的实现顺序):

  1. 参数校验:确认--out非空、过滤规则非空、composeminimal搭配等约束;
  2. 元数据读取:加载内置默认元数据或--in指定的.winmd(文件/目录/内存字节),目录输入只收集扩展名为winmd的文件(crates/libs/bindgen/src/lib.rs);
  3. 引用解析:注册隐式兄弟 crate 引用(非 sys 风格);
  4. 类型选择:解析过滤条目,根据过滤粒度选择TypeClosure(精确)或TypeMap(宽泛/package)管线,产出类型集合;
  5. 派生与实现规划:计算额外 derive、--implement的实现 trait、仅事件委托集合;
  6. 写出:按布局(模块/扁平/包)渲染 token 流,调用 rustfmt 格式化,写入目标文件。

仓库的绑定生成器工具 crates/tools/bindings/src/main.rs 是这一管线的规模化应用:一次cargo run即可按--etc清单重新生成resultregistrystringscorecollections等多个 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),仅供参考

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

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

立即咨询