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:
@ -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,
|
||||||
|
|||||||
@ -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(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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]
|
||||||
|
|||||||
Reference in New Issue
Block a user