windows_bindgen/
lib.rs

1#![doc = include_str!("../readme.md")]
2#![expect(
3    non_upper_case_globals,
4    non_camel_case_types,
5    dead_code,
6    non_snake_case,
7    clippy::enum_variant_names,
8    clippy::upper_case_acronyms
9)]
10
11mod config;
12mod derive;
13mod derive_writer;
14mod filter;
15mod guid;
16mod index;
17mod io;
18mod libraries;
19mod param;
20mod references;
21mod signature;
22mod tables;
23mod tokens;
24mod type_map;
25mod type_name;
26mod type_tree;
27mod types;
28mod value;
29mod warnings;
30mod winmd;
31
32use config::*;
33use derive::*;
34use derive_writer::*;
35use filter::*;
36use guid::*;
37use io::*;
38pub use libraries::*;
39use param::*;
40use references::*;
41use signature::*;
42use std::cmp::Ordering;
43use std::collections::*;
44use std::fmt::Write;
45use tables::*;
46use tokens::*;
47use type_map::*;
48use type_name::*;
49use type_tree::*;
50use types::*;
51use value::*;
52pub use warnings::*;
53use winmd::*;
54mod method_names;
55use method_names::*;
56
57/// The conventional way of calling the `bindgen` function is as follows:
58///
59/// ```rust,no_run
60/// let args = [
61///     "--out",
62///     "src/bindings.rs",
63///     "--filter",
64///     "GetTickCount",
65/// ];
66///
67/// windows_bindgen::bindgen(args).unwrap();
68/// ```
69///
70/// Here is a list of supported arguments.
71///
72/// | Argument | Description |
73/// |----------|-------------|
74/// | `--in` | .winmd files or directories to include. |
75/// | `--out` | File name where the generated bindings will be saved. |
76/// | `--filter` | APIs to include or exclude in the generated bindings. |
77/// | `--rustfmt` | Overrides the default Rust formatting. |
78/// | `--derive` | Extra traits for types to derive. |
79/// | `--flat` | Avoids the default namespace-to-module conversion. |
80/// | `--no-allow` | Avoids generating the default `allow` attribute. |
81/// | `--no-comment` | Avoids generating the code generation comment. |
82/// | `--no-deps` | Avoids dependencies on the various `windows-*` crates. |
83/// | `--sys` | Generates raw or sys-style Rust bindings. |
84/// | `--implement` | Includes implementation traits for WinRT interfaces. |
85/// | `--link` | Overrides the default `windows-link` implementation for system calls. |
86///
87///
88/// # `--out`
89///
90/// Exactly one `--out` argument is required and instructs the `bindgen` function where to write the bindings.
91///
92/// # `--filter`
93///
94/// At least one `--filter` is required and indicates what APIs to include in the generated bindings.
95/// The following will, for example, also include the `Sleep` function:
96///
97/// ```rust
98/// let args = [
99///     "--out",
100///     "src/bindings.rs",
101///     "--filter",
102///     "GetTickCount",
103///     "Sleep",
104/// ];
105/// ```
106///
107/// The `--filter` argument can refer to the function or type name and nothing more. You can also refer
108/// to the namespace that the API metadata uses to group functions and types:
109///
110/// ```rust
111/// let args = [
112///     "--out",
113///     "src/bindings.rs",
114///     "--filter",
115///     "Windows.Foundation.Numerics",
116///     "!Windows.Foundation.Numerics.Matrix3x2",
117/// ];
118/// ```
119///
120/// In this example, all types from the `Windows.Foundation.Numerics` namepace are included with the
121/// exception of `Matrix3x2` which is excluded due to the `!` preamble.
122///
123/// # `--in`
124///
125/// `--in` can indicate a .winmd file or directory containing .winmd files. Alternatively, the special
126/// "default" input can be used to include the particular .winmd files that ship with the `windows-bindgen`
127/// crate. This may used to combine the default metadata with specific .winmd files.
128///
129/// ```rust
130/// let args = [
131///     "--in",
132///     "default",
133///     "Sample.winmd",
134///     "--out",
135///     "src/bindings.rs",
136///     "--filter",
137///     "Sample",
138/// ];
139/// ```
140///
141/// # `--flat`
142///
143/// By default, the bindings include a mapping of namespaces to modules. Consider this example again:
144///
145/// ```rust
146/// let args = [
147///     "--out",
148///     "src/bindings.rs",
149///     "--filter",
150///     "GetTickCount",
151///     "Sleep",
152/// ];
153/// ```
154///
155/// The resulting bindings might look something like this:
156///
157/// ```rust
158/// pub mod Windows {
159///     pub mod Win32 {
160///         pub mod System {
161///             pub mod SystemInformation {
162///                 #[inline]
163///                 pub unsafe fn GetTickCount() -> u32 {
164///                     windows_link::link!("kernel32.dll" "system" fn GetTickCount() -> u32);
165///                     unsafe { GetTickCount() }
166///                 }
167///             }
168///             pub mod Threading {
169///                 #[inline]
170///                 pub unsafe fn Sleep(dwmilliseconds: u32) {
171///                     windows_link::link!("kernel32.dll" "system" fn Sleep(dwmilliseconds : u32));
172///                     unsafe { Sleep(dwmilliseconds) }
173///                 }
174///             }
175///         }
176///     }
177/// }
178/// ```
179///
180/// That's because the default metadata defines `GetTickCount` in the `Windows.Win32.System.SystemInformation`
181/// namespace while `Sleep` is defined in the `Windows.Win32.System.Threading` namespace. Fortunately, it's
182/// easy to turn that off by using the `--flat` argument:
183///
184/// ```rust
185/// let args = [
186///     "--out",
187///     "src/bindings.rs",
188///     "--flat",
189///     "--filter",
190///     "GetTickCount",
191///     "Sleep",
192/// ];
193/// ```
194///
195/// The resulting bindings now look something like this:
196///
197/// ```rust
198/// #[inline]
199/// pub unsafe fn GetTickCount() -> u32 {
200///     windows_link::link!("kernel32.dll" "system" fn GetTickCount() -> u32);
201///     unsafe { GetTickCount() }
202/// }
203/// #[inline]
204/// pub unsafe fn Sleep(dwmilliseconds: u32) {
205///     windows_link::link!("kernel32.dll" "system" fn Sleep(dwmilliseconds : u32));
206///     unsafe { Sleep(dwmilliseconds) }
207/// }
208/// ```
209///
210/// # `--no-allow`
211///
212/// The bindings also include an allow attribute that covers various common warnings inherent in
213/// generated bindings.
214///
215/// ```rust
216/// #![allow(
217///     non_snake_case,
218///     non_upper_case_globals,
219///     non_camel_case_types,
220///     dead_code,
221///     clippy::all
222/// )]
223/// ```
224///
225/// You can prevent this from being generated if you prefer to manage this yourself with the `--no-allow`
226/// argument.
227///
228/// # `--sys`
229///
230/// The `--sys` argument instruct the `bindgen` function to generate raw, sometimes called sys-style Rust
231/// bindings.
232///
233/// ```rust
234/// let args = [
235///     "--out",
236///     "src/bindings.rs",
237///     "--flat",
238///     "--sys",
239///     "--filter",
240///     "GetTickCount",
241///     "Sleep",
242/// ];
243/// ```
244///
245/// The resulting bindings now look something like this:
246///
247/// ```rust
248/// windows_link::link!("kernel32.dll" "system" fn GetTickCount() -> u32);
249/// windows_link::link!("kernel32.dll" "system" fn Sleep(dwmilliseconds : u32));
250/// ```
251///
252/// You'll notice that the bindings are simpler as there's no wrapper functions and other
253/// conveniences. You just need to add a dependency on the tiny [windows-link](https://crates.io/crates/windows-link) crate and you're all set.
254///
255#[track_caller]
256#[must_use]
257pub fn bindgen<I, S>(args: I) -> Warnings
258where
259    I: IntoIterator<Item = S>,
260    S: AsRef<str>,
261{
262    let args = expand_args(args);
263    let mut kind = ArgKind::None;
264    let mut input = Vec::new();
265    let mut include = Vec::new();
266    let mut exclude = Vec::new();
267    let mut references = Vec::new();
268    let mut derive = Vec::new();
269
270    let mut flat = false;
271    let mut no_allow = false;
272    let mut no_comment = false;
273    let mut no_deps = false;
274    let mut no_toml = false;
275    let mut package = false;
276    let mut implement = false;
277    let mut specific_deps = false;
278    let mut rustfmt = String::new();
279    let mut output = String::new();
280    let mut sys = false;
281    let mut link = String::new();
282    let mut index = false;
283
284    for arg in &args {
285        if arg.starts_with('-') {
286            kind = ArgKind::None;
287        }
288
289        match kind {
290            ArgKind::None => match arg.as_str() {
291                "--in" => kind = ArgKind::Input,
292                "--out" => kind = ArgKind::Output,
293                "--filter" => kind = ArgKind::Filter,
294                "--rustfmt" => kind = ArgKind::Rustfmt,
295                "--reference" => kind = ArgKind::Reference,
296                "--derive" => kind = ArgKind::Derive,
297                "--flat" => flat = true,
298                "--no-allow" => no_allow = true,
299                "--no-comment" => no_comment = true,
300                "--no-deps" => no_deps = true,
301                "--no-toml" => no_toml = true,
302                "--package" => package = true,
303                "--sys" => sys = true,
304                "--implement" => implement = true,
305                "--specific-deps" => specific_deps = true,
306                "--link" => kind = ArgKind::Link,
307                "--index" => index = true,
308                _ => panic!("invalid option `{arg}`"),
309            },
310            ArgKind::Output => {
311                if output.is_empty() {
312                    output = arg.to_string();
313                } else {
314                    panic!("exactly one `--out` is required");
315                }
316            }
317            ArgKind::Input => input.push(arg.as_str()),
318            ArgKind::Filter => {
319                if let Some(rest) = arg.strip_prefix('!') {
320                    exclude.push(rest);
321                } else {
322                    include.push(arg.as_str());
323                }
324            }
325            ArgKind::Reference => {
326                references.push(ReferenceStage::parse(arg));
327            }
328            ArgKind::Derive => {
329                derive.push(arg.as_str());
330            }
331            ArgKind::Rustfmt => rustfmt = arg.to_string(),
332            ArgKind::Link => link = arg.to_string(),
333        }
334    }
335
336    if link.is_empty() {
337        if sys || specific_deps {
338            link = "windows_link".to_string();
339        } else {
340            link = "windows_core".to_string();
341        }
342    }
343
344    if package && flat {
345        panic!("cannot combine `--package` and `--flat`");
346    }
347
348    if input.is_empty() {
349        input.push("default");
350    };
351
352    if output.is_empty() {
353        panic!("exactly one `--out` is required");
354    };
355
356    if !sys && !no_deps {
357        references.insert(
358            0,
359            ReferenceStage::parse("windows_collections,flat,Windows.Foundation.Collections"),
360        );
361        references.insert(
362            0,
363            ReferenceStage::parse("windows_numerics,flat,Windows.Foundation.Numerics"),
364        );
365        references.insert(
366            0,
367            ReferenceStage::parse("windows_future,flat,Windows.Foundation.Async*"),
368        );
369        references.insert(
370            0,
371            ReferenceStage::parse("windows_future,flat,Windows.Foundation.IAsync*"),
372        );
373    }
374
375    // This isn't strictly necessary but avoids a common newbie pitfall where all metadata
376    // would be generated when building a component for a specific API.
377    if include.is_empty() {
378        panic!("at least one `--filter` required");
379    }
380
381    let reader = Reader::new(expand_input(&input));
382    let filter = Filter::new(&reader, &include, &exclude);
383    let references = References::new(&reader, references);
384    let types = TypeMap::filter(&reader, &filter, &references);
385    let derive = Derive::new(&reader, &types, &derive);
386    let warnings = WarningBuilder::default();
387
388    let config = Config {
389        types: &types,
390        flat,
391        references: &references,
392        derive: &derive,
393        no_allow,
394        no_comment,
395        no_deps,
396        no_toml,
397        package,
398        rustfmt: &rustfmt,
399        output: &output,
400        sys,
401        implement,
402        specific_deps,
403        link: &link,
404        warnings: &warnings,
405        namespace: "",
406    };
407
408    let tree = TypeTree::new(&types);
409
410    config.write(tree);
411
412    if index {
413        index::write(&types, &format!("{output}/features.json"));
414    }
415
416    warnings.build()
417}
418
419enum ArgKind {
420    None,
421    Input,
422    Output,
423    Filter,
424    Rustfmt,
425    Reference,
426    Derive,
427    Link,
428}
429
430#[track_caller]
431fn expand_args<I, S>(args: I) -> Vec<String>
432where
433    I: IntoIterator<Item = S>,
434    S: AsRef<str>,
435{
436    // This function is needed to avoid a recursion limit in the Rust compiler.
437    #[track_caller]
438    fn from_string(result: &mut Vec<String>, value: &str) {
439        expand_args(result, value.split_whitespace().map(|arg| arg.to_string()))
440    }
441
442    #[track_caller]
443    fn expand_args<I, S>(result: &mut Vec<String>, args: I)
444    where
445        I: IntoIterator<Item = S>,
446        S: AsRef<str>,
447    {
448        let mut expand = false;
449
450        for arg in args.into_iter().map(|arg| arg.as_ref().to_string()) {
451            if arg.starts_with('-') {
452                expand = false;
453            }
454            if expand {
455                for args in io::read_file_lines(&arg) {
456                    if !args.starts_with("//") {
457                        from_string(result, &args);
458                    }
459                }
460            } else if arg == "--etc" {
461                expand = true;
462            } else {
463                result.push(arg);
464            }
465        }
466    }
467
468    let mut result = vec![];
469    expand_args(&mut result, args);
470    result
471}
472
473#[track_caller]
474fn expand_input(input: &[&str]) -> Vec<File> {
475    #[track_caller]
476    fn expand_input(result: &mut Vec<String>, input: &str) {
477        let path = std::path::Path::new(input);
478
479        if path.is_dir() {
480            let prev_len = result.len();
481
482            for path in path
483                .read_dir()
484                .unwrap_or_else(|_| panic!("failed to read directory `{input}`"))
485                .flatten()
486                .map(|entry| entry.path())
487            {
488                if path.is_file()
489                    && path
490                        .extension()
491                        .is_some_and(|extension| extension.eq_ignore_ascii_case("winmd"))
492                {
493                    result.push(path.to_string_lossy().to_string());
494                }
495            }
496
497            if result.len() == prev_len {
498                panic!("failed to find .winmd files in directory `{input}`");
499            }
500        } else {
501            result.push(input.to_string());
502        }
503    }
504
505    let mut paths = vec![];
506    let mut use_default = false;
507
508    for input in input {
509        if *input == "default" {
510            use_default = true;
511        } else {
512            expand_input(&mut paths, input);
513        }
514    }
515
516    let mut input = vec![];
517
518    if use_default {
519        input = [
520            std::include_bytes!("../default/Windows.winmd").to_vec(),
521            std::include_bytes!("../default/Windows.Win32.winmd").to_vec(),
522            std::include_bytes!("../default/Windows.Wdk.winmd").to_vec(),
523        ]
524        .into_iter()
525        .map(|bytes| File::new(bytes).unwrap())
526        .collect();
527    }
528
529    for path in &paths {
530        let Ok(bytes) = std::fs::read(path) else {
531            panic!("failed to read binary file `{path}`");
532        };
533
534        let Some(file) = File::new(bytes) else {
535            panic!("failed to read .winmd format `{path}`");
536        };
537
538        input.push(file);
539    }
540
541    input
542}
543
544fn namespace_starts_with(namespace: &str, starts_with: &str) -> bool {
545    namespace.starts_with(starts_with)
546        && (namespace.len() == starts_with.len()
547            || namespace.as_bytes().get(starts_with.len()) == Some(&b'.'))
548}
549
550#[cfg(test)]
551mod tests {
552    use super::*;
553
554    #[test]
555    fn test_starts_with() {
556        assert!(namespace_starts_with(
557            "Windows.Win32.Graphics.Direct3D11on12",
558            "Windows.Win32.Graphics.Direct3D11on12"
559        ));
560        assert!(namespace_starts_with(
561            "Windows.Win32.Graphics.Direct3D11on12",
562            "Windows.Win32.Graphics"
563        ));
564        assert!(!namespace_starts_with(
565            "Windows.Win32.Graphics.Direct3D11on12",
566            "Windows.Win32.Graphics.Direct3D11"
567        ));
568        assert!(!namespace_starts_with(
569            "Windows.Win32.Graphics.Direct3D",
570            "Windows.Win32.Graphics.Direct3D11"
571        ));
572    }
573}