Trouble with Winapi SendMessageW with WM_GETTEXT

Hi everyone, I'm quite new to rust coming from a python background.

I'm trying to use the Winapi crate to find windows and get the text from them.

I've been able to get the window titles using the GetWindowTextW function but getting the Window text is proving to be tricky.

In order to do this I'm fairly sure I need to use the WM_GETTEXTLENGTH and WM_GETTEXT window messages using the SendMessageW function.

The following WM_GETTEXTLENGTH code works:

    let text_length = SendMessageW(window_handle, WM_GETTEXTLENGTH, 0, 0);

But I'm stuck on the WM_GETTEXT:

    let return_text = SendMessageW(window_handle, WM_GETTEXT, text_length as usize, LPARAM);

The function expects an UINT as the LPARAM but I believe I need to provide a string buffer for the returned text to be copied to?

I found the following on the winapi github repo which appears to be related but I have no idea how to implement it:
in Rust such casts must be done explicitly

Any help would be greatly appreciated.

Cheers

So you need to cast a buffer pointer to LPARAM? Try something like this:

use winapi::shared::minwindef::LPARAM;

let mut buffer = Vec::<u16>::with_capacity(text_length);
let return_text = SendMessageW(window_handle, WM_GETTEXT, text_length as usize, buffer.as_mut_ptr() as LPARAM);

Btw you can write code in these forums using backticks. Blocks of code are created like so:

```
Code goes here
```
1 Like

Thanks chrisd. I appreciate it. I've added the backticks :slight_smile:

I tried your code and now I don't get an error but I don't get the expected output when trying to get the window text using SendMessageW with WM_GETTEXT.

The result looks correct as in the length of the string, but the the buffer is empty.

I can get the window title though when using GetWindowTextW.

I know the window handles are correct as I'm using winspy to verify.

Anyway this is the complete code:

extern crate winapi;
use winapi::{
		shared::{
			minwindef::{
				LPARAM,
				TRUE, 
				BOOL
			},
			windef::HWND,
		},
		um::{
			winuser::{
				EnumChildWindows,
				FindWindowA, 
				GetWindowTextW, 
				GetWindowTextLengthW,
				WM_GETTEXTLENGTH,
				WM_GETTEXT,
				SendMessageW
			},
		},
	};
use std::ffi::CString;

fn main() {
	let window_name = CString::new("*Untitled - Notepad").unwrap();
	unsafe {
		let parent_handle = FindWindowA(std::ptr::null_mut(), window_name.as_ptr());
		
		let title_length = GetWindowTextLengthW(parent_handle) + 1;
		let mut title_buffer = Vec::<u16>::with_capacity(title_length as usize);
		
		let title_result = GetWindowTextW(parent_handle, title_buffer.as_mut_ptr(), title_length);
		title_buffer.set_len((title_result) as usize);
		let title_string = String::from_utf16_lossy(&title_buffer);
		
		let mut temp_buffer = Vec::<u16>::with_capacity(title_length as usize);
		let temp_result = SendMessageW(parent_handle, WM_GETTEXT, title_length as usize, temp_buffer.as_mut_ptr() as LPARAM);
		let temp_string = String::from_utf16_lossy(&temp_buffer);
		
		println!("Parent Handle: {:?}\n\tTitle Length: {:?}\n\tTitle Buffer: {:?}\n\tTitle Result: {:?}\n\tTitle String: {:?}\n\t\tTemp Buffer: {:?}\n\t\tTemp Result: {:?}\n\t\tTemp String: {:?}",&parent_handle, title_length, title_buffer, title_result, title_string, temp_buffer, temp_result, temp_string);

		EnumChildWindows(parent_handle, Some(enumerate_callback), 0isize);
	}
}

unsafe extern "system" fn enumerate_callback(hwnd: HWND, _: LPARAM) -> BOOL {
		let text_length = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0) + 1;
		
		let mut text_buffer = Vec::<u16>::with_capacity(text_length as usize);
		let text_result = SendMessageW(hwnd, WM_GETTEXT, text_length as usize, text_buffer.as_mut_ptr() as LPARAM);
		let text_string = String::from_utf16_lossy(&text_buffer);
		
		println!("Child Handle: {:?}\n\tText Length: {:?}\n\tText Buffer: {:?}\n\tText Result: {:?}\n\tText String: {:?}",&hwnd, text_length, text_buffer, text_result, text_string);
		
		return TRUE;
	}

I think the buffers need a call to set_len after the WM_GETTEXT message otherwise they don't know they've been written to. For example:

let mut temp_buffer = Vec::<u16>::with_capacity(title_length as usize);
let temp_result = SendMessageW(parent_handle, WM_GETTEXT, title_length as usize, temp_buffer.as_mut_ptr() as LPARAM);
temp_buffer.set_len(temp_result as usize);
let temp_string = String::from_utf16_lossy(&temp_buffer);

It might also be good practice to create a safe wrapper around sending the WM_GETTEXT message so you don't have to keep writing the same code. Perhaps something like this:

fn wm_get_text(hwnd: HWND) -> String {
    unsafe {
        let text_length = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0) + 1;
        // Return early if text is an empty string.
        if text_length == 1 {
            return String::new();
        }
        let mut buffer = Vec::<u16>::with_capacity(text_length as usize);
        let buffer_length = SendMessageW(hwnd, WM_GETTEXT, buffer.capacity(), buffer.as_mut_ptr() as LPARAM);
        buffer.set_len(buffer_length as usize);
        
        String::from_utf16_lossy(&buffer)
    }
}
2 Likes

Thanks again chrisd, the missing set_len was the problem. Now I know a bit more about buffers.

And thanks for the tip about good practice. I appreciate it. :grinning:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.