Optimizing the build with Rust/WinRT

In Getting started with Rust/WinRT we used the import macro to generate Rust bindings for Windows APIs directly into the Rust module where the import macro is used. This can be a nested module if you wish. Here’s an example using the Windows.System.Diagnostics namespace, which is documented here.

mod bindings {
    winrt::import!(
        dependencies
            os
        types
            windows::system::diagnostics::*
    );
}

Notice how in the following main function, I now use bindings as part of the Rust path for the windows::system::diagnostics module:

fn main() -> winrt::Result<()> {
    use bindings::windows::system::diagnostics::*;

    for process in ProcessDiagnosticInfo::get_for_processes()? {
        println!(
            "id: {:5} packaged: {:5} name: {}",
            process.process_id()?,
            process.is_packaged()?,
            process.executable_file_name()?
        );
    }

    Ok(())
}

This will give you a quick dump of the processes currently running on your machine:

C:\sample>cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 10.54s
     Running `target\debug\sample.exe`
id:     4 packaged: false name: System
id:   176 packaged: false name: Secure System
id:   284 packaged: false name: Registry
id:  8616 packaged: true  name: RuntimeBroker.exe
id: 10732 packaged: false name: svchost.exe
.
.
.

As I pointed out last time, the time it takes can quickly become prohibitive. One option is to implement the bindings module in a separate bindings.rs file. While this will give a marginal improvement, Cargo is far better at caching the results if you stick the code in its own crate. Back in the console, let’s add a sub crate to house the generated bindings.

C:\sample>cargo new --lib bindings
     Created library `bindings` package

We then need to update the outer project to tell Cargo that it now depends on this new bindings library. To do that, we need to add bindings as a dependency in the Cargo.toml file for the sample project:

[dependencies]
winrt = "0.7.0"
bindings = { path = "bindings" }

While the first dependency is resolved via crates.io the bindings dependency uses a relative path to find the sub crate. This is all it takes to ensure that cargo will automatically build and cache the new dependency. Now let’s get the bindings library configured to import the WinRT types. Inside the bindings project, open the Cargo.toml file where we can add the winrt dependency:

[dependencies]
winrt = "0.7.0"

We can then simply remove the import macro from the original project’s main.rs source file and add it to the bindings project’s lib.rs source file:

winrt::import!(
    dependencies
        os
    types
        windows::system::diagnostics::*
);

The first time I run the example, it completes in about 10 seconds:

C:\sample>cargo run
   Compiling bindings v0.1.0 (C:\sample\bindings)
   Compiling sample v0.1.0 (C:\sample)
    Finished dev [unoptimized + debuginfo] target(s) in 10.61s
     Running `target\debug\sample.exe`
id:     4 packaged: false name: System
id:   176 packaged: false name: Secure System
.
.
.

Now let’s make a small change to see whether incremental build time improves. Back in the sample project’s main function, we can turn the vector returned by get_for_processes into an iterator and limit the results to the first 5 processes:

fn main() -> winrt::Result<()> {
    use bindings::windows::system::diagnostics::*;

    for process in ProcessDiagnosticInfo::get_for_processes()?
        .into_iter()
        .take(5)
    {
        println!(
            "id: {:5} packaged: {:5} name: {}",
            process.process_id()?,
            process.is_packaged()?,
            process.executable_file_name()?
        );
    }

    Ok(())
}

Cargo does quick work of recompiling and gets us running in under a second:

C:\sample>cargo run
   Compiling sample v0.1.0 (C:\sample)
    Finished dev [unoptimized + debuginfo] target(s) in 0.69s
     Running `target\debug\sample.exe`
id:     4 packaged: false name: System
id:   176 packaged: false name: Secure System
id:   284 packaged: false name: Registry
id:  1064 packaged: false name: smss.exe
id:  1484 packaged: false name: csrss.exe

Now that’s much better. But there’s more! Using the import macro has a few drawbacks, but I’ll talk about creating your own build script next time. So stay tuned!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s