Search code examples
winapirustwallpaper

Why does calling SystemParametersInfo from Rust to set the wallpaper set it to black?


I am trying to set the Windows background in Rust using the winapi crate and SystemParametersInfo, but it sets the background to black. In C++, that usually means that pvParam isn't passed correctly or it has the wrong type. What's wrong?

#[cfg(windows)]
extern crate winapi;

use winapi::ctypes::c_void;

use winapi::um::winuser::{SystemParametersInfoA, SPIF_UPDATEINIFILE, SPI_SETDESKWALLPAPER};

fn main() {
    let mut image_path = "Path to Image";
    let image_path_c_ptr: *mut c_void = &mut image_path as *mut _ as *mut c_void;

    unsafe {
        SystemParametersInfoA(
            SPI_SETDESKWALLPAPER,
            0,
            image_path_c_ptr,
            SPIF_UPDATEINIFILE,
        );
    }
}

Solution

  • Rust strings are not C strings. You should instead use CString to interface with C code:

    use std::ffi::CString;
    
    // use ...
    
    fn main() {
        let mut image_path = CString::new("Path to Image").unwrap();
    
        unsafe {
            SystemParametersInfoA(
                SPI_SETDESKWALLPAPER,
                0,
                image_path.as_ptr() as *mut c_void,
                SPIF_UPDATEINIFILE,
            );
        }
    }
    

    To elaborate: image_path is a &str (a fat pointer). By taking a mutable reference to it you are getting a &mut &str. You then pass it to C, which will dereference the pointer and get a &str.

    But C code does not know how to deal with a Rust type: it is only aware of C strings and instead expects a pointer to the first byte. It also expects the string to be NUL terminated, which Rust strings are not. Thus it makes no sense to pass a Rust &str to C code in this case and this is exactly the reason CStr and CString exist.