Skip to content

Instantly share code, notes, and snippets.

@leiless
Last active August 29, 2023 08:59
Show Gist options
  • Save leiless/c3908b5d93f620a15978f6ee73d93875 to your computer and use it in GitHub Desktop.
Save leiless/c3908b5d93f620a15978f6ee73d93875 to your computer and use it in GitHub Desktop.
Rust: windows-rs: SHGetLocalizedName() example
const MAX_PATH_LEN: usize = 2048;
#[allow(overflowing_literals)]
const ERR_MOD_NOT_FOUND: windows::core::HRESULT = windows::core::HRESULT(0x8007007Ei32);
pub fn win_sh_get_localized_name(path: &str) -> anyhow::Result<String> {
let path_hstr = windows::core::HSTRING::from(path);
let mut res_path = Vec::with_capacity(MAX_PATH_LEN);
unsafe { res_path.set_len(res_path.capacity()); }
let mut res_id = 0i32;
unsafe {
windows::Win32::UI::Shell::SHGetLocalizedName(
&path_hstr,
res_path.as_mut_slice(),
std::ptr::addr_of_mut!(res_id),
)?;
}
let mod_path0 = windows::core::PCWSTR::from_raw(res_path.as_ptr());
let mut mod_path = vec![];
loop {
let bytes_returned = unsafe {
windows::Win32::System::Environment::ExpandEnvironmentStringsW(mod_path0, Some(mod_path.as_mut_slice()))
};
if bytes_returned == 0 {
unsafe { windows::Win32::Foundation::GetLastError()?; }
panic!("ExpandEnvironmentStringsW() returned 0 and GetLastError() returns 0");
}
if mod_path.len() >= bytes_returned as _ {
break;
}
// If the function succeeds, the return value is the number of TCHARs stored in the destination buffer, including the terminating null character.
// If the destination buffer is too small to hold the expanded string, the return value is the required buffer size, in characters.
// https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-expandenvironmentstringsw#return-value
mod_path.reserve(bytes_returned as usize - mod_path.len());
unsafe { mod_path.set_len(mod_path.capacity()); }
}
let mod_path = windows::core::PCWSTR::from_raw(mod_path.as_ptr());
let hmod = unsafe {
match windows::Win32::System::LibraryLoader::LoadLibraryW(mod_path) {
Ok(hmod) => hmod,
Err(err) => {
if err.code() == ERR_MOD_NOT_FOUND {
if res_id == 0 {
// C:\Users\nutstore\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\desktop.ini:
// [LocalizedFileNames]
// OneDrive.lnk=OneDrive
let localized_name = mod_path0.to_string()?;
if !localized_name.is_empty() {
return Ok(localized_name);
}
}
}
return Err(err.into());
}
}
};
// Must be of sufficient length to hold a pointer (8 bytes).
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadstringw#parameters
let mut buffer = Vec::with_capacity(8);
loop {
let bytes_returned = unsafe {
windows::Win32::UI::WindowsAndMessaging::LoadStringW(
hmod,
res_id as _,
windows::core::PWSTR::from_raw(buffer.as_mut_ptr()),
buffer.len() as _,
)
};
assert!(bytes_returned >= 0, "{}", bytes_returned);
if bytes_returned == 0 {
unsafe { windows::Win32::Foundation::GetLastError()?; }
panic!("LoadLibraryW() returned 0 and GetLastError() returns 0");
}
if buffer.len() >= bytes_returned as _ {
break;
}
// The number of characters in the string resource that lpBuffer points to (if cchBufferMax is zero).
// The string resource is not guaranteed to be null-terminated in the module's resource table ...
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadstringw#return-value
buffer.reserve(bytes_returned as usize - buffer.len() + 1);
unsafe { buffer.set_len(buffer.capacity()); }
buffer.fill(0);
}
let localized_name = windows::core::PCWSTR::from_raw(buffer.as_ptr());
let localized_name = unsafe { localized_name.to_string()? };
Ok(localized_name)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
//let input = r#"C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Accessories\Notepad.lnk"#;
//let input = r#"C:\Users\nutstore\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\System Tools\File Explorer.lnk"#;
let input = r#"C:\Users\nutstore\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\OneDrive.lnk"#;
//let input = r#"C:\Users\nutstore\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Visual Studio Code\Visual Studio Code.lnk"#;
//let input = r#"C:\Users\nutstore\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\回收站.lnk"#;
println!(" Input: {}", input);
let localized_name = win_sh_get_localized_name(input)?;
println!("Output: {}", localized_name);
Ok(())
}
@leiless
Copy link
Author

leiless commented Aug 29, 2023

$ ./rust-test.exe
 Input: C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Accessories\Notepad.lnk
Output: 记事本

If the localized name cannot be found or the path does not exist, the error would be:
Error: 系统找不到指定的文件。 (0x80070002 = -2147024894i32)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment