In this article, we will explore how to create a basic Windows application using the Windows API (WinAPI) in Rust. Rust is a systems programming language that emphasizes safety and performance, making it a perfect candidate for working with the low-level WinAPI.
To follow this tutorial, you’ll need the following:
First, let’s create a new Rust project:
$ cargo new --bin winapi_app
$ cd winapi_app
Next, add the necessary dependencies to your Cargo.toml
file:
[dependencies]
winapi = { version = "0.3", features = ["winuser", "wingdi"] }
This will include the winapi
crate with the winuser
and wingdi
features enabled.
Now, let’s build the skeleton for our basic Windows application. Create a new Rust file named main.rs
inside the src
folder, and add the following code:
extern crate winapi;
use std::ffi::OsStr;
use std::iter::once;
use std::os::windows::ffi::OsStrExt;
use std::ptr::null_mut;
use winapi::shared::minwindef::{LPARAM, LRESULT, UINT, WPARAM};
use winapi::shared::windef::HWND;
use winapi::um::libloaderapi::GetModuleHandleW;
use winapi::um::winuser::{
CreateWindowExW, DefWindowProcW, GetMessageW, LoadCursorW, PostQuitMessage, RegisterClassW,
ShowWindow, TranslateMessage, DispatchMessageW, MSG, WNDCLASSW, CS_OWNDC, CS_HREDRAW, CS_VREDRAW, WM_DESTROY,
WS_OVERLAPPEDWINDOW, WS_VISIBLE, IDC_ARROW, SW_SHOW, CW_USEDEFAULT,
};
fn main() {
let app_name = to_wstring("WinAPIApp");
let h_instance = unsafe { GetModuleHandleW(null_mut()) };
let wnd_class = WNDCLASSW {
style: CS_OWNDC | CS_HREDRAW | CS_VREDRAW,
lpfnWndProc: Some(window_proc),
hInstance: h_instance,
lpszClassName: app_name.as_ptr(),
cbClsExtra: 0,
cbWndExtra: 0,
hIcon: null_mut(),
hCursor: unsafe { LoadCursorW(null_mut(), IDC_ARROW) },
hbrBackground: null_mut(),
lpszMenuName: null_mut(),
};
let class_atom = unsafe { RegisterClassW(&wnd_class) };
let hwnd = unsafe {
CreateWindowExW(
0,
class_atom as *const u16,
app_name.as_ptr(),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
null_mut(),
null_mut(),
h_instance,
null_mut(),
)
};
if hwnd.is_null() {
panic!("Failed to create window.");
}
unsafe { ShowWindow(hwnd, SW_SHOW) };
let mut msg = MSG {
hwnd: null_mut(),
message: 0,
wParam: 0,
lParam: 0,
time: 0,
pt: Default::default(),
};
while unsafe { GetMessageW(&mut msg, null_mut(), 0, 0) } != 0 {
unsafe {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
}
unsafe extern "system" fn window_proc(
hwnd: HWND,
msg: UINT,
w_param: WPARAM,
l_param: LPARAM,
) -> LRESULT {
match msg {
WM_DESTROY => {
PostQuitMessage(0);
0
}
_ => DefWindowProcW(hwnd, msg, w_param, l_param),
}
}
fn to_wstring(s: &str) -> Vec<u16> {
OsStr::new(s)
.encode_wide()
.chain(once(0))
.collect()
}
This code defines a minimal Windows application that creates a window and handles the WM_DESTROY
message to close the application gracefully. The to_wstring
function is a utility to convert Rust strings to wide-character strings compatible with the WinAPI.
Now that we have our basic Windows application, let’s run it:
$ cargo run
A new window should appear with thetitle “WinAPIApp”. You can interact with the window, minimize, maximize, and close it. This is a basic foundation for building more complex applications with WinAPI and Rust.
Let’s break down the code a bit further to understand the core components of our application.
The WNDCLASSW
structure describes the properties of the window class, such as its style, cursor, and background. The RegisterClassW
function registers the window class with the system:
let wnd_class = WNDCLASSW {
style: CS_OWNDC | CS_HREDRAW | CS_VREDRAW,
lpfnWndProc: Some(window_proc),
hInstance: h_instance,
lpszClassName: app_name.as_ptr(),
cbClsExtra: 0,
cbWndExtra: 0,
hIcon: null_mut(),
hCursor: unsafe { LoadCursorW(null_mut(), IDC_ARROW) },
hbrBackground: null_mut(),
lpszMenuName: null_mut(),
};
let class_atom = unsafe { RegisterClassW(&wnd_class) };
After registering the window class, we create the window using the CreateWindowExW
function:
let hwnd = unsafe {
CreateWindowExW(
0,
class_atom as *const u16,
app_name.as_ptr(),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
null_mut(),
null_mut(),
h_instance,
null_mut(),
)
};
This function creates an overlapped, visible window with the default position and size.
A Windows application relies on a message loop to handle messages sent by the system or other applications. We use the GetMessageW
, TranslateMessage
, and DispatchMessageW
functions to create our message loop:
let mut msg = MSG {
hwnd: null_mut(),
message: 0,
wParam: 0,
lParam: 0,
time: 0,
pt: Default::default(),
};
while unsafe { GetMessageW(&mut msg, null_mut(), 0, 0) } != 0 {
unsafe {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
The window_proc
function is the callback function that processes messages sent to the window. In our case, we handle the WM_DESTROY
message to close the application when the user clicks the close button:
unsafe extern "system" fn window_proc(
hwnd: HWND,
msg: UINT,
w_param: WPARAM,
l_param: LPARAM,
) -> LRESULT {
match msg {
WM_DESTROY => {
PostQuitMessage(0);
0
}
_ => DefWindowProcW(hwnd, msg, w_param, l_param),
}
}
In this tutorial, we built a basic Windows application using Rust and the WinAPI. This foundation can be expanded upon to build more complex applications. Rust’s emphasis on safety and performance makes it an excellent choice for working with low-level APIs such as WinAPI.