1
0

refactor(windows): update Windows API calls and error handling

- Replace `Default::default()` with `std::ptr::null_mut()` or `std::ptr::null()` for FFI calls
- Change `WideCStr::from_ptr` to `WideStr::from_ptr` for better safety
- Remove redundant error variants in `LoadStrRcError` and `ExpandEnvVarError`
- Reorder imports for better readability
- Add test cases for `StrRc` and `ExpandString` utilities
- Improve documentation comments for icon loading functions
This commit is contained in:
2025-10-27 20:49:04 +08:00
parent 81fd224236
commit 6be27def80
3 changed files with 46 additions and 26 deletions

View File

@ -9,7 +9,7 @@ use std::path::Path;
use std::str::FromStr; use std::str::FromStr;
use std::sync::LazyLock; use std::sync::LazyLock;
use thiserror::Error as TeError; use thiserror::Error as TeError;
use widestring::{WideCStr, WideCString, WideChar}; use widestring::{WideCString, WideChar, WideStr};
use windows_sys::Win32::UI::WindowsAndMessaging::HICON; use windows_sys::Win32::UI::WindowsAndMessaging::HICON;
// region: Windows Resource Reference String // region: Windows Resource Reference String
@ -217,7 +217,7 @@ pub struct IconRc {
impl IconRc { impl IconRc {
/// Load icon from executable or `.ico` file. /// Load icon from executable or `.ico` file.
/// ///
/// If you want to extract icon from `.ico` file, please pass `0` to `index` parameter. /// If you want to extract icon from `.ico` file, please pass `0` to `index` parameter.
/// Otherwise `index` is the icon resource index located in executable. /// Otherwise `index` is the icon resource index located in executable.
pub fn new(file: &Path, index: u32, kind: IconSizeKind) -> Result<Self, LoadIconRcError> { pub fn new(file: &Path, index: u32, kind: IconSizeKind) -> Result<Self, LoadIconRcError> {
@ -231,10 +231,10 @@ impl IconRc {
let rv = unsafe { let rv = unsafe {
match kind { match kind {
IconSizeKind::Small => { IconSizeKind::Small => {
ExtractIconExW(file.as_ptr(), index, Default::default(), icon_ptr, 1) ExtractIconExW(file.as_ptr(), index, std::ptr::null_mut(), icon_ptr, 1)
} }
IconSizeKind::Large => { IconSizeKind::Large => {
ExtractIconExW(file.as_ptr(), index, icon_ptr, Default::default(), 1) ExtractIconExW(file.as_ptr(), index, icon_ptr, std::ptr::null_mut(), 1)
} }
} }
}; };
@ -247,7 +247,7 @@ impl IconRc {
} }
/// An alias to default constructor. /// An alias to default constructor.
/// It automatically handle the index parameter for you /// It automatically handle the index parameter for you
/// when loading `.ico` file, rather than executable file. /// when loading `.ico` file, rather than executable file.
pub fn with_ico_file(file: &Path, kind: IconSizeKind) -> Result<Self, LoadIconRcError> { pub fn with_ico_file(file: &Path, kind: IconSizeKind) -> Result<Self, LoadIconRcError> {
Self::new(file, 0, kind) Self::new(file, 0, kind)
@ -290,8 +290,6 @@ impl Drop for IconRc {
pub enum LoadStrRcError { pub enum LoadStrRcError {
/// Given path has embedded NUL. /// Given path has embedded NUL.
EmbeddedNul(#[from] widestring::error::ContainsNul<WideChar>), EmbeddedNul(#[from] widestring::error::ContainsNul<WideChar>),
/// The C-format string is invalid.
BadString(#[from] widestring::error::NulError<WideChar>),
/// The encoding of string is invalid. /// The encoding of string is invalid.
BadEncoding(#[from] widestring::error::Utf16Error), BadEncoding(#[from] widestring::error::Utf16Error),
/// Error when casting integer /// Error when casting integer
@ -303,33 +301,41 @@ pub enum LoadStrRcError {
} }
pub struct StrRc { pub struct StrRc {
inner: String inner: String,
} }
impl StrRc { impl StrRc {
pub fn new(file: &Path, index: u32) -> Result<Self, LoadStrRcError> { pub fn new(file: &Path, index: u32) -> Result<Self, LoadStrRcError> {
use windows_sys::core::PWSTR;
use windows_sys::Win32::UI::WindowsAndMessaging::LoadStringW;
use windows_sys::Win32::Foundation::FreeLibrary; use windows_sys::Win32::Foundation::FreeLibrary;
use windows_sys::Win32::System::LibraryLoader::{LoadLibraryExW, LOAD_LIBRARY_AS_DATAFILE, LOAD_LIBRARY_AS_IMAGE_RESOURCE}; use windows_sys::Win32::System::LibraryLoader::{
LOAD_LIBRARY_AS_DATAFILE, LOAD_LIBRARY_AS_IMAGE_RESOURCE, LoadLibraryExW,
};
use windows_sys::Win32::UI::WindowsAndMessaging::LoadStringW;
use windows_sys::core::PWSTR;
// Load library first // Load library first
let file = WideCString::from_os_str(file.as_os_str())?; let file = WideCString::from_os_str(file.as_os_str())?;
let hmodule = unsafe { LoadLibraryExW(file.as_ptr(), Default::default(), LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE) }; let hmodule = unsafe {
LoadLibraryExW(
file.as_ptr(),
std::ptr::null_mut(),
LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE,
)
};
if hmodule.is_null() { if hmodule.is_null() {
return Err(LoadStrRcError::LoadLibrary); return Err(LoadStrRcError::LoadLibrary);
} }
// Load string // Load string
let mut buffer: *const u16 = Default::default(); let mut buffer: *const u16 = std::ptr::null();
let buffer_ptr= &mut buffer as *mut *const u16 as PWSTR; let buffer_ptr = &mut buffer as *mut *const u16 as PWSTR;
let char_count = unsafe { LoadStringW(hmodule, index, buffer_ptr, 0) }; let char_count = unsafe { LoadStringW(hmodule, index, buffer_ptr, 0) };
// We write this function to make sure following "FreeLibrary" must be executed. // We write this function to make sure following "FreeLibrary" must be executed.
fn load_string(buffer: *const u16, char_count: i32) -> Result<String, LoadStrRcError> { fn load_string(buffer: *const u16, char_count: i32) -> Result<String, LoadStrRcError> {
if char_count == 0 { if char_count == 0 {
Err(LoadStrRcError::LoadString) Err(LoadStrRcError::LoadString)
} else { } else {
let buffer = unsafe { WideCStr::from_ptr(buffer, char_count.try_into()?)? }; let buffer = unsafe { WideStr::from_ptr(buffer, char_count.try_into()?) };
Ok(buffer.to_string()?) Ok(buffer.to_string()?)
} }
} }
@ -372,16 +378,14 @@ impl ParseExpandStrError {
pub enum ExpandEnvVarError { pub enum ExpandEnvVarError {
/// Given string has embedded NUL. /// Given string has embedded NUL.
EmbeddedNul(#[from] widestring::error::ContainsNul<WideChar>), EmbeddedNul(#[from] widestring::error::ContainsNul<WideChar>),
/// Error occurs when executing Win32 expand function. /// The encoding of string is invalid.
ExpandFunction, BadEncoding(#[from] widestring::error::Utf16Error),
/// Error occurs when int type casting. /// Error occurs when int type casting.
BadIntCast(#[from] std::num::TryFromIntError), BadIntCast(#[from] std::num::TryFromIntError),
/// Integeral arithmatic downflow. /// Integeral arithmatic downflow.
Underflow, Underflow,
/// The C-format string is invalid. /// Error occurs when executing Win32 expand function.
BadString(#[from] widestring::error::NulError<WideChar>), ExpandFunction,
/// The encoding of string is invalid.
BadEncoding(#[from] widestring::error::Utf16Error),
/// Some environment vairable are not expanded. /// Some environment vairable are not expanded.
NoEnvVar, NoEnvVar,
} }
@ -410,7 +414,7 @@ impl ExpandString {
// Fetch the size of expand result // Fetch the size of expand result
let source = WideCString::from_str(self.inner.as_str())?; let source = WideCString::from_str(self.inner.as_str())?;
let size = unsafe { ExpandEnvironmentStringsW(source.as_ptr(), Default::default(), 0) }; let size = unsafe { ExpandEnvironmentStringsW(source.as_ptr(), std::ptr::null_mut(), 0) };
if size == 0 { if size == 0 {
return Err(ExpandEnvVarError::ExpandFunction); return Err(ExpandEnvVarError::ExpandFunction);
} }
@ -428,7 +432,7 @@ impl ExpandString {
} }
// Cast result as Rust string // Cast result as Rust string
let wstr = unsafe { WideCStr::from_ptr(buffer.as_ptr(), len_no_nul)? }; let wstr = unsafe { WideStr::from_ptr(buffer.as_ptr(), len_no_nul) };
let rv = wstr.to_string()?; let rv = wstr.to_string()?;
// If the final string still has environment variable, // If the final string still has environment variable,

View File

@ -86,8 +86,8 @@ pub fn notify_assoc_changed() -> () {
SHChangeNotify( SHChangeNotify(
SHCNE_ASSOCCHANGED as i32, SHCNE_ASSOCCHANGED as i32,
SHCNF_IDLIST, SHCNF_IDLIST,
Default::default(), std::ptr::null(),
Default::default(), std::ptr::null(),
) )
} }
} }

View File

@ -48,16 +48,32 @@ fn test_icon_rc() {
// We pick it from "jpegfile" ProgId // We pick it from "jpegfile" ProgId
tester("imageres.dll", 72); tester("imageres.dll", 72);
tester("notepad.exe", 0);
} }
#[test] #[test]
fn test_str_rc() { fn test_str_rc() {
fn tester(file: &str, index: u32) {
let rv = StrRc::new(Path::new(file), index);
assert!(rv.is_ok());
}
// We pick from "jpegfile" ProgId
tester("shell32.dll", 30596);
} }
#[test] #[test]
fn test_expand_string() { fn test_expand_string() {
fn tester(s: &str) {
let rv = ExpandString::new(s);
assert!(rv.is_ok());
let rv = rv.unwrap();
let rv = rv.expand_string();
assert!(rv.is_ok());
}
tester(r#"%SystemRoot%\System32\shell32.dll"#);
} }
#[test] #[test]