winapi 0.3

For over a year people have been wondering when the next version of winapi would be published, and today I am happy to announce that winapi 0.3 has finally been published to crates.io! This new version has a lot of changes, so this post will go over them and cover any changes that will need to be made to your own code when migrating to this new version.

The -sys crates are dead. Long live the winapi behemoth.

In previous versions of winapi, function bindings were separated into their own independent crates based on which import library contained those symbols. For example, the MessageBoxW function is exported from user32.lib, and was thus bound in the user32-sys crate.

In 0.3, this is no longer true: the winapi crate contains all types, constants, macros, and functions. As such, dependencies on all -sys crates in the winapi family should be removed, and all imports updated to point towards winapi itself.

All of the -sys crates on crates.io are now deprecated and may eventually be yanked in the future to prevent new projects from accidentally using them. If you still have a project using these, it will continue to work, but it’s highly recommended you migrate to winapi 0.3 when possible to take advantage of the various improvements and gain access to new bindings.

Items are now organised into modules.

Definitions in winapi were previously not organised into modules. This allowed you to use a type, or constant without having to consider where it was defined. If C code or documentation mentioned HWND, then it was available as winapi::HWND, irrespective of the C header required.

In 0.3, all items are organised into modules based on the Windows SDK header file that defines them. [] If you are familiar with the file structure of the Windows SDK headers, winapi now directly mirrors that. For example, HWND is now located at shared::windef::HWND. To continue the previous example, MessageBoxW, which is defined by winuser.h, can now be found in the um::winuser module.

You can determine where an item (or the module that corresponds to a particular header) is located by searching the online winapi 0.3 documentation. Most translated headers will be in the um (user-mode) top-level module, items shared between user- and kernel-mode code in the shared module, Visual C++-specific items in the vc module, and WinRT-related items in the winrt module.

If given a choice between using names from both shared and um (as the latter re-exports items from the former), and you are not writing code intended to run in the kernel (i.e. device drivers), stick to um. There is no difference between the two, but using the um modules better matches the conventions of writing user-mode code.

Modules gated on feature flags

All binding modules are gated behind Cargo feature flags. This means that in order to access the contents of a module, you must first enable the corresponding feature flag on the winapi crate. For example, to use MessageBoxW in the um::winuser module, you must enable the winuser feature.

Note that enabling a module also enables any modules it depends on for types. For example, MessageBoxW’s definition requires HWND. As a result, enabling the winuser feature also enables (among others) the windef feature, so that HWND is defined.

Features also control which DLLs will be linked. Enabling a module will automatically link any libraries needed by functions defined in that module. For example, enabling winuser will automatically cause your program to be linked against user32.lib, resulting in a runtime dependency on user32.dll.

There is also an everything feature that enables all other feature gates. Enabling everything will adversely affect compile times. []

Compile time improvements

One of the biggest complaints about 0.2 was compile times. Due to winapi being such a common dependency on Windows, users would often find their builds waiting on winapi before cargo could move on to compiling the rest of their project. Given that winapi is still tiny compared to the sheer amount of definitions in the Windows SDK, something had to be done to improve compile times.

Thanks to a lot of hard work optimizing the way symbols are defined, compile times have dropped significantly. Despite 0.3.0 having over twice as much code as 0.2.8, compile times have dropped from 23 seconds to 18 seconds with all features enabled.

However, if you only enable the features that you need, compile times drop to almost negligible levels. For example if you just enable the winuser feature, it will take less than 4 seconds to compile. Because modules are disabled by default, as new headers are added to winapi your compile time will remain constant!

COM interfaces

All COM interfaces now implement the Interface trait, which will allow better ComPtr abstractions. Currently this trait exposes a single method which returns the IID of the interface, essential for QueryInterface wrappers. For example, to get the IID of the IUnknown interface, call IUnknown::uuidof().

Enumerations

Previously, each enum was translated to a “newtype” structure. For example, the definition of POINTER_INPUT_TYPE in winapi 0.2 was equivalent to:

pub struct POINTER_INPUT_TYPE(pub u32);
pub const PT_POINTER: POINTER_INPUT_TYPE = POINTER_INPUT_TYPE(0x00000001);
pub const PT_TOUCH: POINTER_INPUT_TYPE = POINTER_INPUT_TYPE(0x00000002);
pub const PT_PEN: POINTER_INPUT_TYPE = POINTER_INPUT_TYPE(0x00000003);
pub const PT_MOUSE: POINTER_INPUT_TYPE = POINTER_INPUT_TYPE(0x00000004);
pub const PT_TOUCHPAD: POINTER_INPUT_TYPE = POINTER_INPUT_TYPE(0x00000005);

For winapi 0.3, enumerations are instead type aliases to the underlying integer type. The primary reason for this is compile time improvements, due to fewer types and significantly fewer trait implementations being produced. The equivalent to the above in 0.3 is:

pub type POINTER_INPUT_TYPE = u32;
pub const PT_POINTER: POINTER_INPUT_TYPE = 0x00000001;
pub const PT_TOUCH: POINTER_INPUT_TYPE = 0x00000002;
pub const PT_PEN: POINTER_INPUT_TYPE = 0x00000003;
pub const PT_MOUSE: POINTER_INPUT_TYPE = 0x00000004;
pub const PT_TOUCHPAD: POINTER_INPUT_TYPE = 0x00000005;

Unions

The way unions are handled has changed. Previously, winapi generated methods for types that contained unions, with a pair of methods per union field. These methods would access a “backing field” stored in the host structure.

For example, in 0.2, the INPUT structure for x86-64 was roughly equivalent to:

#[cfg(target_arch = "x86_64")]
struct INPUT {
    pub type_: ::DWORD,
    pub u: [u64; 4],
}

impl INPUT {
    pub unsafe fn mi(&self) -> &MOUSEINPUT { transmute(&self.u) }
    pub unsafe fn ki(&self) -> &KEYBDINPUT { transmute(&self.u) }
    pub unsafe fn hi(&self) -> &HARDWAREINPUT { transmute(&self.u) }

    pub unsafe fn mi_mut(&mut self) -> &mut MOUSEINPUT { transmute(&mut self.u) }
    pub unsafe fn ki_mut(&mut self) -> &mut KEYBDINPUT { transmute(&mut self.u) }
    pub unsafe fn hi_mut(&mut self) -> &mut HARDWAREINPUT { transmute(&mut self.u) }
}

/// Access the `mi` field of an `INPUT`.
fn input_mi(inp: &INPUT) -> &MOUSEINPUT {
    inp.mi()
}

In 0.3, unions are now represented as a distinct type, with the methods defined on said union types, rather than on the host structure types. The 0.3 equivalent to the above is:

struct INPUT {
    pub type_: DWORD,
    pub u: INPUT_u,
}

#[cfg(target_arch = "x86_64")]
pub struct INPUT_u([u64; 4]);

impl INPUT_u {
    pub unsafe fn mi(&self) -> &MOUSEINPUT { transmute(self) }
    pub unsafe fn ki(&self) -> &KEYBDINPUT { transmute(self) }
    pub unsafe fn hi(&self) -> &HARDWAREINPUT { transmute(self) }

    pub unsafe fn mi_mut(&mut self) -> &mut MOUSEINPUT { transmute(self) }
    pub unsafe fn ki_mut(&mut self) -> &mut KEYBDINPUT { transmute(self) }
    pub unsafe fn hi_mut(&mut self) -> &mut HARDWAREINPUT { transmute(self) }
}

/// Access the `mi` field of an `INPUT`.
fn input_mi(inp: &INPUT) -> &MOUSEINPUT {
    inp.u.mi()
}

Note that code which accessed union members needs to be modified.

This change was made to bring winapi unions more in line with language-level unions (stabilised in Rust 1.19). Although winapi 0.3 does not use these native unions (in order to support older versions of Rust), the next semver-incompatible version of winapi will switch to native unions.

Finally, the explicit guidance now is to initialise union types with mem::zeroed. Again, this is to ease the future transition to native unions.

Standard library support and c_void.

winapi now defaults to not linking against the standard library. There is only one situation in which this will be a problem: when you wish to use winapi definitions with other code that uses the c_void type.

winapi defines a number of C-compatible types in the top-level ctypes module. All of these are aliases to the “real” type (e.g. type c_char = i8;). The exception is c_void which does not have a single, corresponding Rust type. As such, by default, winapi defines its own c_void type.

However, this c_void will not be compatible with c_void as used by any other code, unless that code is specifically using winapi’s type. This can be fixed by enabling the std feature, which causes winapi::ctypes::c_void to instead alias to std::os::raw::c_void.

MinGW import libraries

One of the strange things about the pc-windows-gnu targets is the fact that it is a self contained toolchain capable of linking Rust projects without any external compilers installed. Unfortunately it only comes with a small selection of import libraries, which meant many system functions would not link by default.

The various -sys crates from the 0.2 era solved this by bundling import libraries from MinGW-w64, however this was a poor solution as those import libraries were woefully out of date, meaning you still could not use any functions introduced with newer versions of Windows. Furthermore, even if you only used the pc-windows-msvc targets, you would still waste a bit of bandwidth downloading these bundled import libraries.

In 0.3 all these import libraries were moved to dedicated import library crates, winapi-x86_64-pc-windows-gnu and winapi-i686-pc-windows-gnu, which winapi has target specific dependencies on in order to prevent cargo from downloading them on targets that don’t need them. In addition, these import libraries are made by me and are up to date with the latest Windows SDK, allowing you to use the hottest new Windows 10 functionality.

Migration example

To make it easier to understand how to migrate your code, here is a simple “Hello, world!” example.

In winapi 0.2 you would have something like the following:

//! Add the following to `Cargo.toml`:
//!
//! ```cargo
//! [dependencies]
//! winapi = "0.2"
//! user32-sys = "0.2"
//! ```

extern crate winapi;
extern crate user32;
use std::io::Error;

fn print_message(msg: &str) -> Result<i32, Error> {
    use std::ffi::OsStr;
    use std::iter::once;
    use std::os::windows::ffi::OsStrExt;
    use std::ptr::null_mut;
    use winapi::MB_OK;
    use user32::MessageBoxW;
    let wide: Vec<u16> = OsStr::new(msg).encode_wide().chain(once(0)).collect();
    let ret = unsafe {
        MessageBoxW(null_mut(), wide.as_ptr(), wide.as_ptr(), MB_OK)
    };
    if ret == 0 { Err(Error::last_os_error()) }
    else { Ok(ret) }
}

fn main() {
    print_message("Hello, world!").unwrap();
}

With winapi 0.3 your code would be changed to this:

//! Add the following to `Cargo.toml`:
//!
//! ```cargo
//! [dependencies.winapi]
//! version = "0.3"
//! features = ["winuser"] # enable the `um::winuser` module.
//!
//! # No dependency on `user32-sys`.
//! ```

extern crate winapi;
// No `extern crate user32;`.
use std::io::Error;

fn print_message(msg: &str) -> Result<i32, Error> {
    use std::ffi::OsStr;
    use std::iter::once;
    use std::os::windows::ffi::OsStrExt;
    use std::ptr::null_mut;
    // `MB_OK` and `MessageBoxW` are in a module.
    use winapi::um::winuser::{MB_OK, MessageBoxW};
    let wide: Vec<u16> = OsStr::new(msg).encode_wide().chain(once(0)).collect();
    let ret = unsafe {
        MessageBoxW(null_mut(), wide.as_ptr(), wide.as_ptr(), MB_OK)
    };
    if ret == 0 { Err(Error::last_os_error()) }
    else { Ok(ret) }
}

fn main() {
    print_message("Hello, world!").unwrap();
}

[†]: This is due to the existence of duplicate, non-compatible definitions in the Windows SDK. The SDK gets around this via abuse of the C pre-processor to redefine names based on the order in which header files are included.

[‡]: You will not just have time to make a cup of coffee. You will have time to fly to the coffee belt, harvest the beans, fly back, roast them, grind them, and then make a cup of coffee.

Written on December 1, 2017