1
0

feat(windows): implement icon and string resource loading

- Add IconRc struct for loading icons from executable or .ico files
- Implement LoadIconRcError for icon loading error handling
- Add StrRc struct for loading string resources from files
- Implement LoadStrRcError for string loading error handling
- Remove old Icon struct and related error types
- Update tests to use new resource loading implementations
- Add Windows Resource section with proper documentation
- Include Win32_System_LibraryLoader feature in Cargo.toml
This commit is contained in:
2025-10-27 14:09:51 +08:00
parent cc79951ee6
commit 81fd224236
4 changed files with 217 additions and 98 deletions

View File

@ -13,6 +13,7 @@ windows-sys = { version = "0.60.2", features = [
"Win32_UI_WindowsAndMessaging",
"Win32_Security",
"Win32_System_Environment",
"Win32_System_LibraryLoader",
"Win32_System_Registry",
"Win32_System_SystemServices",
] }

View File

@ -187,6 +187,172 @@ impl FromStr for StrRefStr {
// endregion
// region: Windows Resource
// region: Icon Resource
/// Error occurs when loading icon.
#[derive(Debug, TeError)]
#[error("error occurs when loading icon resource")]
pub enum LoadIconRcError {
/// Given path has embedded NUL.
EmbeddedNul(#[from] widestring::error::ContainsNul<WideChar>),
/// Error occurs when executing Win32 extract function.
ExtractIcon,
}
/// The size kind of loaded icon
#[derive(Debug, Clone, Copy)]
pub enum IconSizeKind {
/// Small Icon
Small,
/// Large Icon
Large,
}
/// The struct representing a loaded icon resource.
pub struct IconRc {
icon: HICON,
}
impl IconRc {
/// Load icon from executable or `.ico` file.
///
/// 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.
pub fn new(file: &Path, index: u32, kind: IconSizeKind) -> Result<Self, LoadIconRcError> {
use windows_sys::Win32::UI::Shell::ExtractIconExW;
let mut icon = HICON::default();
let icon_ptr = &mut icon as *mut HICON;
let file = WideCString::from_os_str(file.as_os_str())?;
let index = index as i32;
let rv = unsafe {
match kind {
IconSizeKind::Small => {
ExtractIconExW(file.as_ptr(), index, Default::default(), icon_ptr, 1)
}
IconSizeKind::Large => {
ExtractIconExW(file.as_ptr(), index, icon_ptr, Default::default(), 1)
}
}
};
if rv != 1 || icon.is_null() {
Err(LoadIconRcError::ExtractIcon)
} else {
Ok(Self { icon })
}
}
/// An alias to default constructor.
/// It automatically handle the index parameter for you
/// when loading `.ico` file, rather than executable file.
pub fn with_ico_file(file: &Path, kind: IconSizeKind) -> Result<Self, LoadIconRcError> {
Self::new(file, 0, kind)
}
pub unsafe fn from_raw(hicon: HICON) -> Self {
Self { icon: hicon }
}
pub fn into_raw(self) -> HICON {
self.icon
}
}
impl IconRc {
pub fn get_icon(&self) -> HICON {
self.icon
}
}
impl Drop for IconRc {
fn drop(&mut self) {
use windows_sys::Win32::UI::WindowsAndMessaging::DestroyIcon;
if !self.icon.is_null() {
unsafe {
DestroyIcon(self.icon);
}
}
}
}
// endregion
// region: String Resource
/// Error occurs when loading string.
#[derive(Debug, TeError)]
#[error("error occurs when loading string resource")]
pub enum LoadStrRcError {
/// Given path has embedded NUL.
EmbeddedNul(#[from] widestring::error::ContainsNul<WideChar>),
/// The C-format string is invalid.
BadString(#[from] widestring::error::NulError<WideChar>),
/// The encoding of string is invalid.
BadEncoding(#[from] widestring::error::Utf16Error),
/// Error when casting integer
CastInteger(#[from] std::num::TryFromIntError),
/// Can no load library including string resource.
LoadLibrary,
/// Fail to load string resource from file.
LoadString,
}
pub struct StrRc {
inner: String
}
impl StrRc {
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::System::LibraryLoader::{LoadLibraryExW, LOAD_LIBRARY_AS_DATAFILE, LOAD_LIBRARY_AS_IMAGE_RESOURCE};
// Load library first
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) };
if hmodule.is_null() {
return Err(LoadStrRcError::LoadLibrary);
}
// Load string
let mut buffer: *const u16 = Default::default();
let buffer_ptr= &mut buffer as *mut *const u16 as PWSTR;
let char_count = unsafe { LoadStringW(hmodule, index, buffer_ptr, 0) };
// We write this function to make sure following "FreeLibrary" must be executed.
fn load_string(buffer: *const u16, char_count: i32) -> Result<String, LoadStrRcError> {
if char_count == 0 {
Err(LoadStrRcError::LoadString)
} else {
let buffer = unsafe { WideCStr::from_ptr(buffer, char_count.try_into()?)? };
Ok(buffer.to_string()?)
}
}
let res_str = load_string(buffer, char_count);
// Unload library
unsafe { FreeLibrary(hmodule) };
// Return value
res_str.map(|s| Self { inner: s })
}
}
impl StrRc {
pub fn get_string(&self) -> &str {
&self.inner
}
}
// endregion
// endregion
// region: Expand String
/// Error occurs when creating Expand String.
@ -297,87 +463,6 @@ impl FromStr for ExpandString {
// endregion
// region: Icon
/// Error occurs when loading icon.
#[derive(Debug, TeError)]
#[error("error occurs when loading icon")]
pub enum LoadIconError {
/// Given path has embedded NUL.
EmbeddedNul(#[from] widestring::error::ContainsNul<WideChar>),
/// Error occurs when executing Win32 extract function.
ExtractIcon,
}
/// The size kind of loaded icon
#[derive(Debug, Clone, Copy)]
pub enum IconSizeKind {
/// Small Icon
Small,
/// Large Icon
Large,
}
/// The struct representing a loaded icon resource.
pub struct Icon {
icon: HICON,
}
impl Icon {
pub fn new(file: &Path, index: i32, kind: IconSizeKind) -> Result<Self, LoadIconError> {
use windows_sys::Win32::UI::Shell::ExtractIconExW;
let mut icon = HICON::default();
let icon_ptr = &mut icon as *mut HICON;
let file = WideCString::from_os_str(file.as_os_str())?;
let rv = unsafe {
match kind {
IconSizeKind::Small => {
ExtractIconExW(file.as_ptr(), index, Default::default(), icon_ptr, 1)
}
IconSizeKind::Large => {
ExtractIconExW(file.as_ptr(), index, icon_ptr, Default::default(), 1)
}
}
};
if rv != 1 || icon.is_null() {
Err(LoadIconError::ExtractIcon)
} else {
Ok(Self { icon })
}
}
pub unsafe fn from_raw(hicon: HICON) -> Self {
Self { icon: hicon }
}
pub fn into_raw(self) -> HICON {
self.icon
}
}
impl Icon {
pub fn get_icon(&self) -> HICON {
self.icon
}
}
impl Drop for Icon {
fn drop(&mut self) {
use windows_sys::Win32::UI::WindowsAndMessaging::DestroyIcon;
if !self.icon.is_null() {
unsafe {
DestroyIcon(self.icon);
}
}
}
}
// endregion
// region: Windows Commandline
// region: Cmd Lexer

View File

@ -1,25 +1,48 @@
use std::path::Path;
use wfassoc::extra::windows::*;
#[test]
fn test_icon_ref_str() {
fn ok_tester(s: &str, probe: (&str, u32)) {
let rv = s.parse::<IconRefStr>();
assert!(rv.is_ok());
let rv = rv.unwrap();
assert_eq!(rv.get_path(), probe.0);
assert_eq!(rv.get_index(), probe.1);
}
fn err_tester(s: &str) {
let rv = s.parse::<IconRefStr>();
assert!(rv.is_err());
}
ok_tester(r#"%SystemRoot%\System32\imageres.dll,-72"#, (r#"%SystemRoot%\System32\imageres.dll"#, 72));
err_tester(r#"C:\Windows\Cursors\aero_arrow.cur"#);
err_tester(r#"@%SystemRoot%\System32\shell32.dll,-30596"#);
}
#[test]
fn test_str_ref_str() {
fn ok_tester(s: &str, probe: (&str, u32)) {
let rv = s.parse::<StrRefStr>();
assert!(rv.is_ok());
let rv = rv.unwrap();
assert_eq!(rv.get_path(), probe.0);
assert_eq!(rv.get_index(), probe.1);
}
fn err_tester(s: &str) {
let rv = s.parse::<StrRefStr>();
assert!(rv.is_err());
}
ok_tester(r#"@%SystemRoot%\System32\shell32.dll,-30596"#, (r#"%SystemRoot%\System32\shell32.dll"#, 30596));
err_tester(r#"This is my application, OK?"#);
err_tester(r#"%SystemRoot%\System32\imageres.dll,-72"#);
}
#[test]
fn test_icon_rc() {
}
#[test]
fn test_str_rc() {
}
#[test]
fn test_expand_string() {
}
#[test]
fn test_icon() {
fn tester(file: &str, index: i32) {
let icon = Icon::new(Path::new(file), index, IconSizeKind::Small);
fn tester(file: &str, index: u32) {
let icon = IconRc::new(Path::new(file), index, IconSizeKind::Small);
assert!(icon.is_ok())
}
@ -27,6 +50,16 @@ fn test_icon() {
tester("imageres.dll", 72);
}
#[test]
fn test_str_rc() {
}
#[test]
fn test_expand_string() {
}
#[test]
fn test_cmd_args() {
// Declare tester