Kenny Kerr is a Software Engineer at Microsoft where he works on C++ and Rust tools and libraries for the Windows operating system. He is the creator of C++/WinRT and Rust for Windows. Kenny also wrote recurring columns and feature articles for C/C++ Users Journal, Visual Studio Magazine, MSDN Magazine, originally called Microsoft Systems Journal.

Getting Started with Rust

Kenny's courses on Pluralsight

Kenny on GitHub

Kenny on YouTube

Kenny on LinkedIn

Kenny's old blog on WordPress

Kenny on the Wayback Machine

Getting Started with Rust

The windows-rs project has been available for some time and while I still have a great deal of work left to do, I thought I should start spending some time writing about Rust for Windows and not simply building Rust for Windows. 😊 As I did for C++/WinRT, I thought I would start writing a few short "how to" or "how it works" articles to help developers understand some of the fundamentals of the windows-rs project.

Some of these topics will be obvious for Rust developers but perhaps not the Windows developer new to Rust. Other topics might be obvious to Windows developers but less so to the seasoned Rust developer new to Windows. Either way, I hope you find it useful. Feel free to open an issue on the repo if you have any questions.

Choosing between the windows and windows-sys crates

The windows crate provides bindings for the Windows API, including C-style APIs like CreateThreadpool as well as COM and WinRT APIs like DirectX. This crate provides the most comprehensive API coverage for the Windows operating system. Where possible, the windows crate also attempts to provide a more idiomatic and safe programming model for Rust developers.

The windows-sys crate provides raw bindings for the C-style Windows APIs. It lacks support for COM and WinRT APIs. The windows-sys crate was born out of the realization that the most expensive aspect of the windows crate, in terms of build time, is the cost of compiling function bodies. The Rust compiler just spends a great deal of effort compiling function bodies, so a version of the windows crate that only includes declarations is both much smaller and faster by comparison. The trouble is that COM-style virtual function calls require extra code gen in Rust (unlike C++) and this in turn leads to slower compile times. Enter the windows-sys crate.

Of course, we continue to work hard at improving performance both in terms of the underlying Rust compiler toolchain as well as the efficiency of the code generated for these crates. We are thus confident that the compile-time will continue to improve.

What do you need?windowswindows-sys
Fast compile times are one of your top concerns✅
You need no_std support✅
You need COM or WinRT support✅
You would prefer to use APIs that feel idiomatic to Rust✅
Minimum supported Rust version1.481.48

How are these crates built?

The windows and windows-sys crates are generated from metadata describing the Windows API. Originally only WinRT APIs included metadata, but metadata is now provided for older C and COM APIs as well. The win32metadata project provides the tools to produce the metadata and the windows-metadata and windows-bindgen crates are used to read the metadata and generate the windows and windows-sys crates. The bindings are generated differently based on the differing goals of the respective crates. You can find the exact metadata file used to generate a particular version of the windows and windows-sys crates here.

How do I find a particular API?

First pick the crate you would like to use. Then search the documentation for the chosen crate:

Note that the docs include a note indicating which features to enable in order to access a particular API.

What APIs are included?

All Windows APIs provided by the Windows SDK are included, with a few exceptions. The definitions of these APIs are collected from metadata and transformed into Rust bindings. The process of generating the Rust bindings purposefully omits a few APIs. APIs are only excluded if they are (1) unsuitable for Rust developers and (2) impose a large hit on the overall size of the windows and windows-sys crates.

The Xaml API is excluded because it is all but unusable without direct language support that only the Xaml team can provide. Xaml is also focused and tailored for C# app development so this API isn't applicable to Rust developers. The MsHtml API is also excluded because it is only intended for Microsoft's older scripting languages like JScript and VBScript. It is also by far the single largest module as measured in lines of code. Beyond that, a few deprecrated and unusable APIs are excluded. You can see exactly what the windows crate excludes and what the windows-sys crate excludes.

Beyond that, the windows-sys crate currently excludes all COM and WinRT APIs. The windows-sys crate only includes declarations and COM and WinRT calls are far too cumbersome without the abstractions provided by the windows crate. Here are some tips for choosing between the windows and windows-sys crates.

Where's my favorite macro from the Windows SDK?

The windows and windows-sys crates are generated from metadata. This metadata only includes type definitions and function signatures, not macros, header-only functions, or function bodies. You may find some equivalents of common C/C++ helper macros and functions in the windows crate, but in general the macros don't have direct equivalents in the windows or windows-sys crates.

Calling your first API with the windows crate

So you want to get a feel for calling a simple Windows API. Where to start? Let's look at a relatively simple API for submitting callbacks to the thread pool. You can read more about this API here.

The first step is to add a dependency on the windows crate and indicate which features you'd like to access:

[dependencies.windows]
version = "0.43"
features = [
    "Win32_Foundation",
    "Win32_System_Threading",
]

Why these two features? Well, the thread pool API is defined in the Win32::System::Threading module and we'll also use a handful of definitions from the Win32::Foundation module. If you're unsure, the docs for any given API provide a helpful comment indicating which features are required. For example, here are the docs for WaitForThreadpoolWorkCallbacks where you can see it depends on both of these features since it is defined in the Win32::System::Threading module and depends on BOOL which is defined in the Win32::Foundation module.

Cargo will now handle the heavy lifting, tracking down the dependencies and making sure the import libs are present, so that we can simply call these APIs in Rust without any further configuration. We can employ a use declaration to make these APIs a little more accessible:

#![allow(unused)]
fn main() {
use windows::{Win32::Foundation::*, Win32::System::Threading::*};
}

In order to "prove" that the code works and yet keep it real simple let's just use the thread pool to increment a counter some number of times. Here we can use a reader-writer lock for safe and multi-threaded access to the counter variable:

#![allow(unused)]
fn main() {
static COUNTER: std::sync::RwLock<i32> = std::sync::RwLock::new(0);
}

For this example, I'll just use a simple main function with a big unsafe block since virtually everything here is going to be unsafe. Why is that? Well the windows crate lets you call foreign functions and these are generally assumed to be unsafe.

fn main() {
    unsafe {
        
    }
}

The thread pool API is modeled as a set of "objects" exposed via a traditional C-style API. The first thing we need to do is create a work object:

#![allow(unused)]
fn main() {
let work = CreateThreadpoolWork(Some(callback), None, None);
}

The first parameter is a pointer to a callback function. The remaining parameters are optional and you can read more about them in my thread pool series on MSDN.

Since this function allocates memory, it is possible that it might fail, and this is indicated by returning a null pointer rather than a valid work object handle. We'll check for this condition and call the GetLastError function to display any relevant error code:

#![allow(unused)]
fn main() {
if work.is_null() {
    println!("{:?}", GetLastError());
    return;
}
}

The callback itself must be a valid C-style callback according to the signature expected by the thread pool API. Here's a simple callback that will increment the count:

#![allow(unused)]
fn main() {
extern "system" fn callback(
    _: *mut TP_CALLBACK_INSTANCE,
    _: *mut std::ffi::c_void,
    _: *mut TP_WORK,
) {
    let mut counter = COUNTER.write().unwrap();
    *counter += 1;
}
}

The parameters can safely be ignored but do come in handy from time to time. At this point, we have a valid work object but nothing is happening yet. In order to kick off some "work", we need to submit the work object to the thread pool. You can do so as many times as you'd like, so lets go ahead and do it ten times:

#![allow(unused)]
fn main() {
for _ in 0..10 {
    SubmitThreadpoolWork(work);
}
}

You can now expect the callbacks to run concurrently, hence the RwLock above. Of course, with all of that concurrency we need some way to tell when the work is done. That's the job of the WaitForThreadpoolWorkCallbacks function:

#![allow(unused)]
fn main() {
WaitForThreadpoolWorkCallbacks(work, false);
}

The second parameter indicates whether we would like to cancel any pending callbacks that have not started to execute. Passing false here thus indicates that we would like the wait function to block until all of the submitted work has completed. At that point, we can safely close the work object to free its memory:

#![allow(unused)]
fn main() {
CloseThreadpoolWork(work);
}

And just to prove that it works reliably, we can print out the counter's value:

#![allow(unused)]
fn main() {
let counter = COUNTER.read().unwrap();
println!("counter: {}", *counter);
}

Running the sample should print something like this:

counter: 10

Here's the full sample for reference.

Calling your first API with the windows-sys crate

So you want to get a feel for calling a simple Windows API. Where to start? Let's look at a relatively simple API for submitting callbacks to the thread pool. You can read more about this API here.

The first step is to add a dependency on the windows-sys crate and indicate which features you'd like to access:

[dependencies.windows-sys]
version = "0.43"
features = [
    "Win32_Foundation",
    "Win32_System_Threading",
]

Why these two features? Well, the thread pool API is defined in the Win32::System::Threading module and we'll also use a handful of definitions from the Win32::Foundation module. If you're unsure, the docs for any given API provide a helpful comment indicating which features are required. For example, here are the docs for WaitForThreadpoolWorkCallbacks where you can see it depends on both of these features since it is defined in the Win32::System::Threading module and depends on BOOL which is defined in the Win32::Foundation module.

Cargo will now handle the heavy lifting, tracking down the dependencies and making sure the import libs are present, so that we can simply call these APIs in Rust without any further configuration. We can employ a use declaration to make these APIs a little more accessible:

#![allow(unused)]
fn main() {
use windows_sys::{Win32::Foundation::*, Win32::System::Threading::*};
}

In order to "prove" that the code works and yet keep it real simple let's just use the thread pool to increment a counter some number of times. Here we can use a reader-writer lock for safe and multi-threaded access to the counter variable:

#![allow(unused)]
fn main() {
static COUNTER: std::sync::RwLock<i32> = std::sync::RwLock::new(0);
}

For this example, I'll just use a simple main function with a big unsafe block since virtually everything here is going to be unsafe. Why is that? Well the windows crate lets you call foreign functions and these are generally assumed to be unsafe.

fn main() {
    unsafe {
        
    }
}

The thread pool API is modeled as a set of "objects" exposed via a traditional C-style API. The first thing we need to do is create a work object:

#![allow(unused)]
fn main() {
let work = CreateThreadpoolWork(Some(callback), std::ptr::null_mut(), std::ptr::null());
}

The first parameter is a pointer to a callback function. The remaining parameters are optional and you can read more about them in my thread pool series on MSDN.

Since this function allocates memory, it is possible that it might fail, and this is indicated by returning a null pointer rather than a valid work object handle. We'll check for this condition and call the GetLastError function to display any relevant error code:

#![allow(unused)]
fn main() {
if work.is_null() {
    println!("{:?}", GetLastError());
    return;
}
}

The callback itself must be a valid C-style callback according to the signature expected by the thread pool API. Here's a simple callback that will increment the count:

#![allow(unused)]
fn main() {
extern "system" fn callback(
    _: *mut TP_CALLBACK_INSTANCE,
    _: *mut std::ffi::c_void,
    _: *mut TP_WORK,
) {
    let mut counter = COUNTER.write().unwrap();
    *counter += 1;
}
}

The parameters can safely be ignored but do come in handy from time to time. At this point, we have a valid work object but nothing is happening yet. In order to kick off some "work", we need to submit the work object to the thread pool. You can do so as many times as you'd like, so lets go ahead and do it ten times:

#![allow(unused)]
fn main() {
for _ in 0..10 {
    SubmitThreadpoolWork(work);
}
}

You can now expect the callbacks to run concurrently, hence the RwLock above. Of course, with all of that concurrency we need some way to tell when the work is done. That's the job of the WaitForThreadpoolWorkCallbacks function:

#![allow(unused)]
fn main() {
WaitForThreadpoolWorkCallbacks(work, 0);
}

The second parameter indicates whether we would like to cancel any pending callbacks that have not started to execute. Passing 0, meaning false, here thus indicates that we would like the wait function to block until all of the submitted work has completed. At that point, we can safely close the work object to free its memory:

#![allow(unused)]
fn main() {
CloseThreadpoolWork(work);
}

And just to prove that it works reliably, we can print out the counter's value:

#![allow(unused)]
fn main() {
let counter = COUNTER.read().unwrap();
println!("counter: {}", *counter);
}

Running the sample should print something like this:

counter: 10

Here's the full sample for reference.

Calling your first COM API

COM APIs are unique in that they expose functionality through interfaces. An interface is just a collection of virtual function pointers grouped together in what is known as a vtable, or virtual function table. This is not something that Rust supports directly, like C++ does, but the windows crate provides the necessary code gen to make it possible and seamless. A COM API will still typically start life through a traditional C-style function call in order to get your hands on a COM interface. From there you might call other methods via the interface.

Some COM-based APIs can get real complicated so let’s start with a very simple example. The CreateUri function is officially documented on MSDN as returning the IUri interface representing the results of parsing the given URI. The Rust docs for the windows crate indicate that it resides in the Win32::System::Com module so we can configure our windows crate dependency accordingly:

[dependencies.windows]
version = "0.43"
features = [
    "Win32_System_Com",
]

And we can employ a use declaration to make this API a little more accessible. The windows crate's core module also provides a few helpers to make it easier to work with COM interfaces, so we’ll include that as well:

#![allow(unused)]
fn main() {
use windows::{core::*, Win32::System::Com::*};
}

For this example, I'll just use a simple main function with a big unsafe block since virtually everything here is going to be unsafe. Why is that? Well the windows crate lets you call foreign functions and these are generally assumed to be unsafe.

fn main() -> Result<()> {
    unsafe {
        
        Ok(())
    }
}

The only "interesting" point here is the use of the Result type from the windows::core module that provides Windows error handling to simplify the following API calls. And with that, we can call the CreateUri function as follows:

#![allow(unused)]
fn main() {
let uri = CreateUri(w!("http://kennykerr.ca"), Uri_CREATE_CANONICALIZE, 0)?;
}

There's quite a lot going on here. The first parameter is actually a PCWSTR, representing a null-terminated wide string used by many Windows APIs. The windows crate provides the handy w! macro for creating a valid null-terminated wide string as a compile-time constant. The second parameter is just the default flag specified by the official documentation. The third parameter is reserved and should thus be zero.

The resulting IUri object has various methods that we can now use to inspect the URI. The official documentation describes the various interface methods and the Rust docs give you a quick glimpse at their various signatures so that you can quickly figure out how to call them in Rust. For this example, let’s just call two of them to print out the URI's domain and the HTTP port number:

#![allow(unused)]
fn main() {
let domain = uri.GetDomain()?;
let port = uri.GetPort()?;

println!("{domain} ({port})");
}

Under the hood, those methods will invoke the virtual functions through the COM interface and into the implementation provided by the API. They also provide a bunch of error and signature transformation to make it very natural to use from Rust. And that’s it, running the sample should print something like this:

kennykerr.ca (80)

Here's the full sample for reference.

Calling your first WinRT API

Windows 8 introduced the Windows Runtime, which at its heart, is just COM with a few more conventions thrown in to make language bindings appear more seamless. The windows crate already makes calling COM APIs far more seamless than it is for C++ developers, but WinRT goes further by providing first-class support for modeling things like constructors, events, and class hierarchies. In calling your first COM API, we saw that you still had to bootsrap the API with a C-style DLL export before calling COM interface methods. WinRT works the same way but abstracts this away in a generalized manner.

Let’s use a simple example to illustrate. The XmlDocument "class" models an XML document that can be loaded from various sources. The Rust docs for the windows crate indicate that this type resides in the Data::Xml::Dom module so we can configure our windows crate dependency as follows:

[dependencies.windows]
version = "0.43" 
features = [
    "Data_Xml_Dom",
]

And we can employ a use declaration to make this API a little more accessible. The windows crate's core module just provides a few helpers to make it easier to work with Windows APIs, so we'll include that as well:

#![allow(unused)]
fn main() {
use windows::{core::*, Data::Xml::Dom::XmlDocument}; 
}

For this example, I'll just use a simple main function with a Result type from the windows::core module to provide automatic error propagation and simplify the subsequent API calls:

fn main() -> Result<()> {

    Ok(())
}

You'll notice that unlike the previous Win32 and COM examples, this main function does not need an unsafe block since WinRT calls are assumed to be safe thanks to its more constrained type-system.

To begin, we can simply call the new method to create a new XmlDocument object:

#![allow(unused)]
fn main() {
let doc = XmlDocument::new()?;
}

This looks a lot more like an idiomatic Rust type than your typical COM API, but under the hood a similar mechanism is used to instantiate the XmlDocument implementation via a DLL export. We can then call the LoadXml method to test it out. There are various other options for loading XML from different sources, which you can read about in the official documentation or from the Rust docs for the XmlDocument API. The windows crate also provides the handy h! macro for creating an HSTRING, the string type used by WinRT APIs:

#![allow(unused)]
fn main() {
doc.LoadXml(h!("<html>hello world</html>"))?;
}

And just like that, we have a fully-formed Xml document that we can inspect. For this example, let's just grab the document element and then do some basic queries as follows:

#![allow(unused)]
fn main() {
let root = doc.DocumentElement()?;
assert!(root.NodeName()? == "html");
println!("{}", root.InnerText()?);
}

First we assert that the element's name is in fact "html" and then print out the element's inner text. As with the previous COM example, those methods all invoke virtual functions through COM interfaces, but the windows crate makes it very simple to make such calls directly from Rust. And that's it, running the sample should print something like this:

hello world

Here's the full sample for reference.

How do I query for a specific COM interface?

COM and WinRT interfaces in the windows crate implement the Interface trait. This trait provides the cast method that will use QueryInterface under the hood to cast the current interface to another interface supported by the object. The cast method returns a Result<T> so that failure can be handled in a natural way in Rust.

For example, it is often necesary to get the IDXGIDevice interface for a given Direct3D device to interop with other rendering APIs. This is how you might create a swap chain for drawing and presenting to a Direct3D device. Let's imagine a simple function that accepts a Direct3D device and returns the underlying DXGI factory:

#![allow(unused)]
fn main() {
fn get_dxgi_factory(device: &ID3D11Device) -> Result<IDXGIFactory2> {
}
}

The first thing you need to do is query or cast the Direct3D device for its DXGI interface as follows:

#![allow(unused)]
fn main() {
let device = device.cast::<IDXGIDevice>()?;
}

If its more convenient, you can also make use of type inference as follows:

#![allow(unused)]
fn main() {
let device: IDXGIDevice = device.cast()?;
}

With the COM interface in hand, we need an unsafe block to call its methods:

#![allow(unused)]
fn main() {
unsafe {
}
}

Within the unsafe block, we can retrieve the device's physical adapter:

#![allow(unused)]
fn main() {
let adapter = device.GetAdapter()?;
}

And just for fun (or debugging), we might print out the adapter's name:

#![allow(unused)]
fn main() {
if cfg!(debug_assertions) {
    let mut desc = Default::default();
    adapter.GetDesc(&mut desc)?;
    println!("{}", String::from_utf16_lossy(&desc.Description));
}
}

Finally, we can return the adapter's parent and also the DXGI factory object for the device:

#![allow(unused)]
fn main() {
adapter.GetParent()
}

Running the sample I get the following impressive results:

AMD FirePro W4100

Here's a more comprehensive DirectX example.

The cast method works equally well for WinRT classes and interfaces. It is particularly useful for interop with WinRT APIs.