winapirustexe

Extracting executable's icon in rust


I'm making some code to extract the 64x64px variant of the icon in an exe file. The following code does work, but it outputs a grayscale 16x16px image. I'm still a bit new to rust so please excuse the bad code. I'm using winapi version 0.3.9 with the features: "winuser", "shellapi".

use std::ffi::CString;
use std::ptr;
use iced::widget::image::Handle;
use winapi::shared::minwindef::DWORD;
use winapi::um::shellapi::ExtractIconA;
use winapi::shared::windef::HICON;
use winapi::um::wingdi::{CreateDIBSection, DeleteObject, GetDIBits, BITMAPINFO, BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS, RGBQUAD};
use winapi::um::winuser::{DestroyIcon, GetIconInfo};
use winapi::um::wingdi::{CreateCompatibleDC, SelectObject};

pub fn exe_icon_to_vec_u8(path: &str) -> Result<Handle, String> {
    let c_path = CString::new(path).map_err(|_| "Invalid path".to_string())?;
    let hicon: HICON = unsafe { ExtractIconA(ptr::null_mut(), c_path.as_ptr(), 0) };
    if hicon.is_null() {
        return Err("Failed to extract icon".into());
    }
    let mut icon_info = unsafe { std::mem::zeroed() };
    if unsafe { GetIconInfo(hicon, &mut icon_info) } == 0 {
        unsafe { DestroyIcon(hicon) };
        return Err("Failed to get icon info".into());
    }
    let mut bmp_info = BITMAPINFO {
        bmiHeader: BITMAPINFOHEADER {
            biSize: std::mem::size_of::<BITMAPINFOHEADER>() as DWORD,
            biWidth: 64, biHeight: -64, biPlanes: 1, biBitCount: 32,
            biCompression: BI_RGB, biSizeImage: 0, biXPelsPerMeter: 0,
            biYPelsPerMeter: 0, biClrUsed: 0, biClrImportant: 0,
        }, bmiColors: [RGBQUAD { rgbBlue: 0, rgbGreen: 0, rgbRed: 0, rgbReserved: 0 }; 1],
    };
    let mut pixels: Vec<u8> = vec![0; 64 * 64 * 4];
    let hdc = unsafe { CreateCompatibleDC(ptr::null_mut()) };
    if hdc.is_null() {
        unsafe {
            DestroyIcon(hicon);
        }
        return Err("Failed to create compatible DC".into());
    }
    let hbitmap = unsafe {
        CreateDIBSection(
            hdc,
            &mut bmp_info,
            DIB_RGB_COLORS,
            &mut pixels.as_mut_ptr() as *mut _ as *mut _,
            ptr::null_mut(),
            0,
        )
    };
    if hbitmap.is_null() {
        unsafe {
            DeleteObject(icon_info.hbmColor as _);
            DeleteObject(icon_info.hbmMask as _);
            DestroyIcon(hicon);
        }
        return Err("Failed to create DIB section".into());
    }
    unsafe {
        SelectObject(hdc, hbitmap as _);
        GetDIBits(
            hdc,
            icon_info.hbmColor,
            0,
            64,
            pixels.as_mut_ptr() as *mut _,
            &mut bmp_info,
            DIB_RGB_COLORS,
        );
        DeleteObject(hbitmap as _);
        DeleteObject(icon_info.hbmColor as _);
        DeleteObject(icon_info.hbmMask as _);
        DestroyIcon(hicon);
    }
    Ok(Handle::from_pixels(64, 64, pixels))
}

I tried to find some cargo packages that do this but with no success.


Solution

  • Here is the answer for anyone in the future:

    use winapi::um::wingdi::{ CreateCompatibleDC, DeleteDC, DeleteObject, GetDIBits, BITMAPINFO, BITMAPINFOHEADER, BI_RGB, RGBQUAD };
    use winapi::um::shellapi::{ SHGetFileInfoW, SHFILEINFOW, SHGFI_ICON, SHGFI_LARGEICON };
    use winapi::um::winbase::{ GlobalAlloc, GlobalLock, GHND, GlobalUnlock, GlobalFree };
    use winapi::um::winuser::{ GetIconInfo, ICONINFO, DestroyIcon };
    use winapi::shared::minwindef::DWORD;
    use iced::widget::image::Handle;
    use widestring::U16CString;
    use std::{mem, ptr, slice};
    
    pub fn extract_icon_as_handle(path: &str) -> Result<Handle, Box<dyn std::error::Error>> {
        unsafe {
            let mut shfi = SHFILEINFOW {
                hIcon: ptr::null_mut(), iIcon: 0,
                dwAttributes: 0,
                szDisplayName: [0; 260],
                szTypeName: [0; 80],
            };
    
            SHGetFileInfoW(
                U16CString::from_str(path)?.as_ptr(),
                0,
                &mut shfi,
                mem::size_of::<SHFILEINFOW>() as DWORD,
                SHGFI_ICON | SHGFI_LARGEICON,
            );
    
            if shfi.hIcon.is_null() {
                return Err("No icon found.".into());
            }
    
            let mut icon_info = ICONINFO {
                fIcon: 0,
                xHotspot: 0,
                yHotspot: 0,
                hbmMask: ptr::null_mut(),
                hbmColor: ptr::null_mut(),
            };
            GetIconInfo(shfi.hIcon, &mut icon_info);
    
            let hdc = CreateCompatibleDC(ptr::null_mut());
    
            let bmp_info_header = BITMAPINFOHEADER {
                biSize: mem::size_of::<BITMAPINFOHEADER>() as DWORD,
                biWidth: 32,
                biHeight: -32, // Negative to indicate a top-down DIB
                biPlanes: 1,
                biBitCount: 32,
                biCompression: BI_RGB as DWORD,
                biSizeImage: 0,
                biXPelsPerMeter: 0,
                biYPelsPerMeter: 0,
                biClrUsed: 0,
                biClrImportant: 0,
            };
    
            let mut bitmap_info = BITMAPINFO {
                bmiHeader: bmp_info_header,
                bmiColors: [RGBQUAD { rgbBlue: 0, rgbGreen: 0, rgbRed: 0, rgbReserved: 0 }; 1],
            };
    
            let bitmap_memory = GlobalAlloc(GHND, (32 * 32 * 4) as usize);
            let bitmap_bits = GlobalLock(bitmap_memory) as *mut u8;
    
            GetDIBits(
                hdc,
                icon_info.hbmColor,
                0,
                32,
                bitmap_bits as *mut _,
                &mut bitmap_info,
                0,
            );
    
            GlobalUnlock(bitmap_memory);
            DeleteDC(hdc);
            DeleteObject(icon_info.hbmColor as _);
            DestroyIcon(shfi.hIcon);
    
            let bitmap_slice = slice::from_raw_parts(bitmap_bits, (32 * 32 * 4) as usize).to_vec();
    
            let mut rgba_slice = vec![0u8; bitmap_slice.len()];
            for i in 0..(32 * 32) {
                let b = bitmap_slice[i * 4 + 0];
                let g = bitmap_slice[i * 4 + 1];
                let r = bitmap_slice[i * 4 + 2];
                let a = bitmap_slice[i * 4 + 3];
                rgba_slice[i * 4 + 0] = r;
                rgba_slice[i * 4 + 1] = g;
                rgba_slice[i * 4 + 2] = b;
                rgba_slice[i * 4 + 3] = a;
            }
    
            GlobalFree(bitmap_memory);
    
            let handle = Handle::from_pixels(32, 32, rgba_slice);
    
            return Ok(handle);
        }
    }