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.