Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

More convenient way to work with strings in winapi calls

I'm looking for more convenient way to work with std::String in winapi calls in Rust.

Using rust v 0.12.0-nigtly with winapi 0.1.22 and user32-sys 0.1.1

Now I'm using something like this:

use winapi;
use user32;

pub fn get_window_title(handle: i32) -> String {
    let mut v: Vec<u16> = Vec::new();
    v.reserve(255);
    let mut p = v.as_mut_ptr();
    let len = v.len();
    let cap = v.capacity();
    let mut read_len = 0;
    unsafe {
        mem::forget(v);
        read_len = unsafe { user32::GetWindowTextW(handle as winapi::HWND, p, 255) };
        if read_len > 0 {
            return String::from_utf16_lossy(Vec::from_raw_parts(p, read_len as usize, cap).as_slice());
        } else {
            return "".to_string();
        }
    }
}

I think, that this vector based memory allocation is rather bizarre. So I'm looking for more easier way to cast LPCWSTR to std::String

like image 673
Cherrionella Avatar asked Jun 16 '15 03:06

Cherrionella


1 Answers

In your situation, you always want a maximum of 255 bytes, so you can use an array instead of a vector. This reduces the entire boilerplate to a mem::uninitialized() call, an as_mut_ptr() call and a slicing operation.

unsafe {
    let mut v: [u16; 255] = mem::uninitialized();
    let read_len = user32::GetWindowTextW(
        handle as winapi::HWND,
        v.as_mut_ptr(),
        255,
    );
    String::from_utf16_lossy(&v[0..read_len])
}

In case you wanted to use a Vec, there's an easier way than to destroy the vec and re-create it. You can write to the Vec's content directly and let Rust handle everything else.

let mut v: Vec<u16> = Vec::with_capacity(255);
unsafe {
    let read_len = user32::GetWindowTextW(
        handle as winapi::HWND,
        v.as_mut_ptr(),
        v.capacity(),
    );
    v.set_len(read_len); // this is undefined behavior if read_len > v.capacity()
    String::from_utf16_lossy(&v)
}

As a side-note, it is idiomatic in Rust to not use return on the last statement in a function, but to simply let the expression stand there without a semicolon. In your original code, the final if-expression could be written as

if read_len > 0 {
    String::from_utf16_lossy(Vec::from_raw_parts(p, read_len as usize, cap).as_slice())
} else {
    "".to_string()
}

but I removed the entire condition from my samples, as it is unnecessary to handle 0 read characters differently from n characters.

like image 189
oli_obk Avatar answered Oct 15 '22 23:10

oli_obk