DEV Community

Raeisi
Raeisi

Posted on

Find Window Handle (HWND) using Rust

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");
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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",
]
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)