In this paper, I will explain how to find a specific handle (HWND) for an application running on Windows using Rust programming language.
In the Windows API term, an HWND (window handle) uniquely identifies a window, serving as an abstract data type representing a window object. One of the first steps for developing applications around Windows Desktop is to find the target app's HWND.
This is what I am going to achieve:
fn main() {
let Some(app_hwn) = find_app_hwnd() else {
eprintln!("target app handle is not found. make sure the app is running");
return;
};
assert_ne!(app_hwn, HWND::default(), "app handle must not be empty");
}
Working with Windows API has never been as easy as these days. The official windows-sys
and windows
crates are here to address the issue. The former contains low-level API call codes while the latter is a wrapper around it simplifying the usage.
Prepare Project
Create a new cargo binary application using:
$ cargo new find_hwnd --bin
Then modify its cargo.toml
file to include the windows
crate dependency with minimum required features:
# cargo.toml
[package]
name = "find_hwnd"
version = "0.1.0"
edition = "2021"
# Windows sys-call interface
[dependencies.windows]
version = "0.58"
features = [
"Win32_Foundation",
"Win32_UI_WindowsAndMessaging",
]
What is the plan?
First, get the current desktop handle using GetDesktopWindow
sys-call. Windows, like other operating systems, can be configured for multiple desktop environments and each desktop may contain many apps. Here the focus is on the current desktop.
Second, iterate over all child apps in the corresponding desktop and filter them by name. The EnumChildWindows
sys-call accepts a callback function, which gets called for each application in the given desktop HWND.
Full code
The following code will find the Task Manager app (TaskManagerWindow).
// main.rs
use std::sync::{Arc, Mutex};
use windows::Win32::Foundation::{BOOL, HWND, LPARAM};
use windows::Win32::UI::WindowsAndMessaging::{EnumChildWindows, GetDesktopWindow, RealGetWindowClassA};
type GetTargetType = Arc<Mutex<Option<HWND>>>;
const TARGET_APP_TITLE: &str = "TaskManagerWindow";
fn main() {
let Some(app_hwn) = find_app_hwnd() else {
eprintln!("target app handle is not found. make sure the app is running");
return;
};
assert_ne!(app_hwn, HWND::default(), "app handle must not be empty");
println!("handle for '{}' is {:?}", TARGET_APP_TITLE, app_hwn);
}
fn find_app_hwnd() -> Option<HWND> {
let storage: GetTargetType = Arc::new(Mutex::new(None));
let l_param = LPARAM(&storage as *const GetTargetType as isize);
unsafe {
let desktop_hwnd = GetDesktopWindow();
let _ = EnumChildWindows(desktop_hwnd, Some(find_target_process), l_param);
}
let state = storage.lock().unwrap();
*state
}
unsafe extern "system" fn find_target_process(hwnd: HWND, l_param: LPARAM) -> BOOL {
let mut buffer = [0_u8; 128];
let read_len = RealGetWindowClassA(hwnd, &mut buffer);
let proc_name = String::from_utf8_lossy(&buffer[..read_len as usize]);
if proc_name != TARGET_APP_TITLE {
return BOOL(1);
}
let storage = &*(l_param.0 as *const GetTargetType);
let mut storage = storage.lock().unwrap();
(*storage).replace(hwnd);
BOOL(1)
}
NOTE: Make sure Task Manager is running before execute.
Sample output would be something like this with a different HWND value:
handle for 'TaskManagerWindow' is HWND(0x609ba)
Code explanation:
The find_app_hwnd
function finds the current desktop HWND and then pushes the system to call find_target_process
for each app. The latter function will get called for each app running. It will call the RealGetWindowClassA
sys-call obtaining the title of the received HWND and doing the filtration.
What about LPARAM?
Most Windows APIs accept an extra parameter called LPARAM with the type of isize
. It was used here to pass the reference of local storage; when the target app is filtered and found, the storage will be recovered from LPARAM and updated. Alternatively to LPARAM, a mutable global static variable can be used.
Reference
You can find the full project code at: https://github.com/raeisimv/rust-practice/tree/main/windows_syscall/x00_find_hwnd
Top comments (0)