diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 4cea1705a0..b8959b991f 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -133,8 +133,6 @@ jobs: run: cargo clippy -p test_array - name: Clippy test_bcrypt run: cargo clippy -p test_bcrypt - - name: Clippy test_bstr - run: cargo clippy -p test_bstr - name: Clippy test_calling_convention run: cargo clippy -p test_calling_convention - name: Clippy test_cfg_generic diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bc2778babd..fe68bd00f3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -159,8 +159,6 @@ jobs: run: cargo test -p test_array --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_bcrypt run: cargo test -p test_bcrypt --target ${{ matrix.target }} ${{ matrix.etc }} - - name: Test test_bstr - run: cargo test -p test_bstr --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_calling_convention run: cargo test -p test_calling_convention --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_cfg_generic @@ -255,10 +253,10 @@ jobs: run: cargo test -p test_riddle --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_standalone run: cargo test -p test_standalone --target ${{ matrix.target }} ${{ matrix.etc }} - - name: Clean - run: cargo clean - name: Test test_string_param run: cargo test -p test_string_param --target ${{ matrix.target }} ${{ matrix.etc }} + - name: Clean + run: cargo clean - name: Test test_strings run: cargo test -p test_strings --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_structs diff --git a/crates/libs/core/src/imp/com_bindings.rs b/crates/libs/core/src/imp/com_bindings.rs index 9e13ac5b8c..2da69bc3d5 100644 --- a/crates/libs/core/src/imp/com_bindings.rs +++ b/crates/libs/core/src/imp/com_bindings.rs @@ -43,7 +43,6 @@ pub const CO_E_NOTINITIALIZED: windows_core::HRESULT = windows_core::HRESULT(0x8 pub const E_BOUNDS: windows_core::HRESULT = windows_core::HRESULT(0x8000000B_u32 as _); pub const E_INVALIDARG: windows_core::HRESULT = windows_core::HRESULT(0x80070057_u32 as _); pub const E_NOINTERFACE: windows_core::HRESULT = windows_core::HRESULT(0x80004002_u32 as _); -pub const E_OUTOFMEMORY: windows_core::HRESULT = windows_core::HRESULT(0x8007000E_u32 as _); pub const E_POINTER: windows_core::HRESULT = windows_core::HRESULT(0x80004003_u32 as _); windows_core::imp::define_interface!( IAgileObject, diff --git a/crates/libs/registry/src/key.rs b/crates/libs/registry/src/key.rs index 9261b4ecca..a001f6fb99 100644 --- a/crates/libs/registry/src/key.rs +++ b/crates/libs/registry/src/key.rs @@ -175,7 +175,7 @@ impl Key { return Err(invalid_data()); } - let mut value = HStringBuilder::new(len / 2)?; + let mut value = HStringBuilder::new(len / 2); unsafe { self.raw_get_bytes(name.as_raw(), value.as_bytes_mut())? }; value.trim_end(); Ok(value.into()) diff --git a/crates/libs/registry/src/value.rs b/crates/libs/registry/src/value.rs index 753e9d416c..a11539ecc1 100644 --- a/crates/libs/registry/src/value.rs +++ b/crates/libs/registry/src/value.rs @@ -111,7 +111,7 @@ impl TryFrom for HSTRING { type Error = Error; fn try_from(from: Value) -> Result { match from.ty { - Type::String | Type::ExpandString => Ok(Self::from_wide(trim(from.data.as_wide()))?), + Type::String | Type::ExpandString => Ok(Self::from_wide(trim(from.data.as_wide()))), _ => Err(invalid_data()), } } diff --git a/crates/libs/strings/Cargo.toml b/crates/libs/strings/Cargo.toml index 2d8d8fb445..303c0a0831 100644 --- a/crates/libs/strings/Cargo.toml +++ b/crates/libs/strings/Cargo.toml @@ -21,11 +21,6 @@ targets = [] version = "0.52.6" path = "../targets" -[dependencies.windows-result] -version = "0.2.0" -path = "../result" -default-features = false - [features] default = ["std"] std = [] diff --git a/crates/libs/strings/readme.md b/crates/libs/strings/readme.md index a58980e81a..75064d0842 100644 --- a/crates/libs/strings/readme.md +++ b/crates/libs/strings/readme.md @@ -21,16 +21,14 @@ use windows_strings::*; const A: PCSTR = s!("ansi"); const W: PCWSTR = w!("wide"); -fn main() -> Result<()> { +fn main() { let b = BSTR::from("bstr"); let h = HSTRING::from("hstring"); assert_eq!(b, "bstr"); assert_eq!(h, "hstring"); - assert_eq!(unsafe { A.to_string()? }, "ansi"); - assert_eq!(unsafe { W.to_string()? }, "wide"); - - Ok(()) + assert_eq!(unsafe { A.to_string().unwrap() }, "ansi"); + assert_eq!(unsafe { W.to_string().unwrap() }, "wide"); } ``` diff --git a/crates/libs/strings/src/bindings.rs b/crates/libs/strings/src/bindings.rs index 5ac479c727..7ad192f339 100644 --- a/crates/libs/strings/src/bindings.rs +++ b/crates/libs/strings/src/bindings.rs @@ -13,8 +13,6 @@ windows_targets::link!("oleaut32.dll" "system" fn SysFreeString(bstrstring : BST windows_targets::link!("oleaut32.dll" "system" fn SysStringLen(pbstr : BSTR) -> u32); pub type BOOL = i32; pub type BSTR = *const u16; -pub const E_OUTOFMEMORY: HRESULT = 0x8007000E_u32 as _; pub type HANDLE = *mut core::ffi::c_void; pub type HEAP_FLAGS = u32; -pub type HRESULT = i32; pub type PCWSTR = *const u16; diff --git a/crates/libs/strings/src/bstr.rs b/crates/libs/strings/src/bstr.rs index dd413449e0..79fd493b95 100644 --- a/crates/libs/strings/src/bstr.rs +++ b/crates/libs/strings/src/bstr.rs @@ -43,23 +43,23 @@ impl BSTR { } /// Create a `BSTR` from a slice of 16 bit characters (wchars). - pub fn from_wide(value: &[u16]) -> Result { + pub fn from_wide(value: &[u16]) -> Self { if value.is_empty() { - return Ok(Self::new()); + return Self::new(); } let result = unsafe { Self(bindings::SysAllocStringLen( value.as_ptr(), - value.len().try_into()?, + value.len().try_into().unwrap(), )) }; if result.is_empty() { - Err(Error::from_hresult(HRESULT(bindings::E_OUTOFMEMORY))) - } else { - Ok(result) + panic!("allocation failed"); } + + result } /// # Safety @@ -77,14 +77,14 @@ impl BSTR { impl Clone for BSTR { fn clone(&self) -> Self { - Self::from_wide(self.as_wide()).unwrap() + Self::from_wide(self.as_wide()) } } impl From<&str> for BSTR { fn from(value: &str) -> Self { let value: alloc::vec::Vec = value.encode_utf16().collect(); - Self::from_wide(&value).unwrap() + Self::from_wide(&value) } } diff --git a/crates/libs/strings/src/hstring.rs b/crates/libs/strings/src/hstring.rs index 6cd0a1ebc4..11a3f5db49 100644 --- a/crates/libs/strings/src/hstring.rs +++ b/crates/libs/strings/src/hstring.rs @@ -44,7 +44,7 @@ impl HSTRING { } /// Create a `HSTRING` from a slice of 16 bit characters (wchars). - pub fn from_wide(value: &[u16]) -> Result { + pub fn from_wide(value: &[u16]) -> Self { unsafe { Self::from_wide_iter(value.iter().copied(), value.len()) } } @@ -61,12 +61,12 @@ impl HSTRING { /// # Safety /// len must not be less than the number of items in the iterator. - unsafe fn from_wide_iter>(iter: I, len: usize) -> Result { + unsafe fn from_wide_iter>(iter: I, len: usize) -> Self { if len == 0 { - return Ok(Self::new()); + return Self::new(); } - let ptr = HStringHeader::alloc(len.try_into()?)?; + let ptr = HStringHeader::alloc(len.try_into().unwrap()); // Place each utf-16 character into the buffer and // increase len as we go along. @@ -79,7 +79,7 @@ impl HSTRING { // Write a 0 byte to the end of the buffer. (*ptr).data.offset((*ptr).len as isize).write(0); - Ok(Self(ptr)) + Self(ptr) } fn as_header(&self) -> Option<&HStringHeader> { @@ -96,7 +96,7 @@ impl Default for HSTRING { impl Clone for HSTRING { fn clone(&self) -> Self { if let Some(header) = self.as_header() { - Self(header.duplicate().unwrap()) + Self(header.duplicate()) } else { Self::new() } @@ -138,7 +138,7 @@ impl core::fmt::Debug for HSTRING { impl From<&str> for HSTRING { fn from(value: &str) -> Self { - unsafe { Self::from_wide_iter(value.encode_utf16(), value.len()).unwrap() } + unsafe { Self::from_wide_iter(value.encode_utf16(), value.len()) } } } @@ -169,7 +169,6 @@ impl From<&std::ffi::OsStr> for HSTRING { std::os::windows::ffi::OsStrExt::encode_wide(value), value.len(), ) - .unwrap() } } } diff --git a/crates/libs/strings/src/hstring_builder.rs b/crates/libs/strings/src/hstring_builder.rs index 7b3d576cee..fcbe4d9881 100644 --- a/crates/libs/strings/src/hstring_builder.rs +++ b/crates/libs/strings/src/hstring_builder.rs @@ -8,14 +8,14 @@ pub struct HStringBuilder(*mut HStringHeader); impl HStringBuilder { /// Creates a preallocated `HSTRING` value. - pub fn new(len: usize) -> Result { - let header = HStringHeader::alloc(len.try_into()?)?; + pub fn new(len: usize) -> Self { + let header = HStringHeader::alloc(len.try_into().unwrap()); if len > 0 { unsafe { core::ptr::write_bytes((*header).data, 0, len) }; } - Ok(Self(header)) + Self(header) } /// Shortens the string by removing any trailing 0 characters. diff --git a/crates/libs/strings/src/hstring_header.rs b/crates/libs/strings/src/hstring_header.rs index afae2c112f..a60edd030e 100644 --- a/crates/libs/strings/src/hstring_header.rs +++ b/crates/libs/strings/src/hstring_header.rs @@ -14,9 +14,9 @@ pub struct HStringHeader { } impl HStringHeader { - pub fn alloc(len: u32) -> Result<*mut Self> { + pub fn alloc(len: u32) -> *mut Self { if len == 0 { - return Ok(core::ptr::null_mut()); + return core::ptr::null_mut(); } // Allocate enough space for header and two bytes per character. @@ -27,7 +27,7 @@ impl HStringHeader { unsafe { bindings::HeapAlloc(bindings::GetProcessHeap(), 0, bytes) } as *mut Self; if header.is_null() { - return Err(Error::from_hresult(HRESULT(bindings::E_OUTOFMEMORY))); + panic!("allocation failed"); } unsafe { @@ -38,7 +38,7 @@ impl HStringHeader { (*header).data = &mut (*header).buffer_start; } - Ok(header) + header } pub unsafe fn free(header: *mut Self) { @@ -49,20 +49,20 @@ impl HStringHeader { bindings::HeapFree(bindings::GetProcessHeap(), 0, header as *mut _); } - pub fn duplicate(&self) -> Result<*mut Self> { + pub fn duplicate(&self) -> *mut Self { if self.flags & HSTRING_REFERENCE_FLAG == 0 { // If this is not a "fast pass" string then simply increment the reference count. self.count.add_ref(); - Ok(self as *const Self as *mut Self) + self as *const Self as *mut Self } else { // Otherwise, allocate a new string and copy the value into the new string. - let copy = Self::alloc(self.len)?; + let copy = Self::alloc(self.len); // SAFETY: since we are duplicating the string it is safe to copy all data from self to the initialized `copy`. // We copy `len + 1` characters since `len` does not account for the terminating null character. unsafe { core::ptr::copy_nonoverlapping(self.data, (*copy).data, self.len as usize + 1); } - Ok(copy) + copy } } } diff --git a/crates/libs/strings/src/lib.rs b/crates/libs/strings/src/lib.rs index 12dc81321e..5e99c2fde0 100644 --- a/crates/libs/strings/src/lib.rs +++ b/crates/libs/strings/src/lib.rs @@ -10,9 +10,6 @@ extern crate alloc; use alloc::string::String; -pub use windows_result::Result; -use windows_result::*; - mod bstr; pub use bstr::*; diff --git a/crates/libs/strings/src/pcwstr.rs b/crates/libs/strings/src/pcwstr.rs index 63196e42b2..7d83620383 100644 --- a/crates/libs/strings/src/pcwstr.rs +++ b/crates/libs/strings/src/pcwstr.rs @@ -70,7 +70,7 @@ impl PCWSTR { /// # Safety /// /// See the safety information for `PCWSTR::as_wide`. - pub unsafe fn to_hstring(&self) -> Result { + pub unsafe fn to_hstring(&self) -> HSTRING { HSTRING::from_wide(self.as_wide()) } diff --git a/crates/libs/strings/src/pwstr.rs b/crates/libs/strings/src/pwstr.rs index 8f4260d416..8a74b5474f 100644 --- a/crates/libs/strings/src/pwstr.rs +++ b/crates/libs/strings/src/pwstr.rs @@ -67,7 +67,7 @@ impl PWSTR { /// # Safety /// /// See the safety information for `PWSTR::as_wide`. - pub unsafe fn to_hstring(&self) -> Result { + pub unsafe fn to_hstring(&self) -> HSTRING { HSTRING::from_wide(self.as_wide()) } diff --git a/crates/tests/bstr/Cargo.toml b/crates/tests/bstr/Cargo.toml deleted file mode 100644 index 96b1d5012a..0000000000 --- a/crates/tests/bstr/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "test_bstr" -version = "0.0.0" -edition = "2021" -publish = false - -[lib] -doc = false -doctest = false - -[dependencies.windows] -path = "../../libs/windows" -features = [ - "Win32_Foundation", -] diff --git a/crates/tests/bstr/src/lib.rs b/crates/tests/bstr/src/lib.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/crates/tests/bstr/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/tests/bstr/tests/bstr.rs b/crates/tests/bstr/tests/bstr.rs deleted file mode 100644 index 622706f1ec..0000000000 --- a/crates/tests/bstr/tests/bstr.rs +++ /dev/null @@ -1,62 +0,0 @@ -use windows::core::*; -use windows::Win32::Foundation::*; - -#[test] -fn test() { - let b: BSTR = "hello".into(); - assert_eq!(b, "hello"); -} - -#[test] -fn clone() { - let a: BSTR = "hello".into(); - assert!(!a.is_empty()); - assert!(a.len() == 5); - let b = a.clone(); - assert_eq!(a, "hello"); - assert_eq!(b, "hello"); - assert_eq!("hello", a); - - let a = BSTR::default(); - assert!(a.is_empty()); - assert!(a.len() == 0); - let b = a.clone(); - assert_eq!(a, ""); - assert_eq!(b, ""); - - let a = BSTR::new(); - assert!(a.is_empty()); - assert!(a.len() == 0); - assert_eq!(a.len(), 0); - assert_eq!(a.as_wide().len(), 0); - - let wide = &[0x68, 0x65, 0x6c, 0x6c, 0x6f]; - let a = BSTR::from_wide(wide).unwrap(); - assert!(!a.is_empty()); - assert!(a.len() == 5); - assert_eq!(a.as_wide().len(), 5); - assert_eq!(a.as_wide(), wide); - assert_eq!(a, "hello"); - - let a: BSTR = "".into(); - assert!(a.is_empty()); - assert!(a.len() == 0); - - let a: BSTR = unsafe { SysAllocStringLen(None) }; - assert!(a.is_empty()); - assert!(a.len() == 0); - - let a = BSTR::from("a"); - assert_eq!(a, String::from("a")); - assert_eq!(String::from("a"), a); -} - -#[test] -fn interop() -> Result<()> { - unsafe { - let b: BSTR = "hello".into(); - SysAddRefString(&b)?; - SysFreeString(&b); - Ok(()) - } -} diff --git a/crates/tests/core/tests/hstring.rs b/crates/tests/core/tests/hstring.rs deleted file mode 100644 index 8016cbf3e0..0000000000 --- a/crates/tests/core/tests/hstring.rs +++ /dev/null @@ -1,311 +0,0 @@ -use windows::core::*; - -#[test] -fn hstring_works() { - assert_eq!(std::mem::size_of::(), std::mem::size_of::()); - let empty = HSTRING::new(); - assert!(empty.is_empty()); - assert!(empty.is_empty()); - - let hello = HSTRING::from("Hello"); - assert!(!hello.is_empty()); - assert!(hello.len() == 5); - - let rust = hello.to_string(); - assert!(rust == "Hello"); - assert!(rust.len() == 5); - - let hello2 = hello.clone(); - assert!(!hello2.is_empty()); - assert!(hello2.len() == 5); - - assert!(HSTRING::from("Hello") == HSTRING::from("Hello")); - assert!(HSTRING::from("Hello") != HSTRING::from("World")); - - assert!(HSTRING::from("Hello") == "Hello"); - assert!(HSTRING::from("Hello") != "Hello "); - assert!(HSTRING::from("Hello") != "Hell"); - assert!(HSTRING::from("Hello") != "World"); - - assert!(HSTRING::from("Hello").to_string() == String::from("Hello")); -} - -#[test] -fn display_format() { - let value = HSTRING::from("Hello world"); - assert!(format!("{}", value) == "Hello world"); -} - -#[test] -fn display_invalid_format() { - let s = HSTRING::from_wide(&[ - 0xD834, 0xDD1E, 0x006d, 0x0075, 0x0073, 0xDD1E, 0x0069, 0x0063, 0xD834, - ]) - .unwrap(); - let d = format!("{}", s); - assert_eq!(d, "𝄞mus�ic�"); -} - -#[test] -fn debug_format() { - let value = HSTRING::from("Hello world"); - assert!(format!("{:?}", value) == r#""Hello world""#); -} - -#[test] -fn from_empty_string() { - let h = HSTRING::from(""); - assert!(format!("{}", h).is_empty()); -} - -#[test] -fn from_os_string_string() { - let wide_data = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0xD800, 0x0069, 0x0063]; - use std::os::windows::prelude::OsStringExt; - let o = std::ffi::OsString::from_wide(wide_data); - let h = HSTRING::from(o); - let d = HSTRING::from_wide(wide_data).unwrap(); - assert_eq!(h, d); -} - -#[test] -fn from_os_str_string() { - let wide_data = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0xD800, 0x0069, 0x0063]; - use std::os::windows::prelude::OsStringExt; - let o = std::ffi::OsString::from_wide(wide_data); - let o = o.as_os_str(); - let h = HSTRING::from(o); - let d = HSTRING::from_wide(wide_data).unwrap(); - assert_eq!(h, d); -} - -#[test] -fn from_path() { - let p = std::path::Path::new("/foo/bar"); - let h = HSTRING::from(p); - assert_eq!(h, "/foo/bar"); -} - -#[test] -fn hstring_to_string() { - let h = HSTRING::from("test"); - let s = String::try_from(h).unwrap(); - assert!(s == "test"); -} - -#[test] -fn hstring_to_string_err() { - // 𝄞muic - let wide_data = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0xD800, 0x0069, 0x0063]; - let h = HSTRING::from_wide(wide_data).unwrap(); - let err = String::try_from(h); - assert!(err.is_err()); -} - -#[test] -fn hstring_to_string_lossy() { - // 𝄞muic - let wide_data = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0xD800, 0x0069, 0x0063]; - let h = HSTRING::from_wide(wide_data).unwrap(); - let s = h.to_string_lossy(); - assert_eq!(s, "𝄞mu�ic"); -} - -#[test] -fn hstring_to_os_string() { - // 𝄞muic - let wide_data = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0xD800, 0x0069, 0x0063]; - let h = HSTRING::from_wide(wide_data).unwrap(); - let s = h.to_os_string(); - use std::os::windows::prelude::OsStringExt; - assert_eq!(s, std::ffi::OsString::from_wide(wide_data)); -} - -#[test] -fn hstring_hashing_equal_strings() { - // Checks if two strings of identical contents have the same hash - use std::hash::{DefaultHasher, Hash, Hasher}; - - let hstring_1 = HSTRING::from("Hello World"); - let hstring_2 = HSTRING::from("Hello World"); - - assert_eq!(hstring_1, hstring_2); - - let mut hasher_1 = DefaultHasher::new(); - let mut hasher_2 = DefaultHasher::new(); - - hstring_1.hash(&mut hasher_1); - hstring_2.hash(&mut hasher_2); - - let h1_hash = hasher_1.finish(); - let h2_hash = hasher_2.finish(); - - assert_eq!(h1_hash, h2_hash); -} - -#[test] -fn hstring_hashing_different_strings() { - // Checks if two strings of different contents have the same hash - use std::hash::{DefaultHasher, Hash, Hasher}; - - let hstring_1 = HSTRING::from("Hello World"); - let hstring_2 = HSTRING::from("Hello World 2"); - - assert_ne!(hstring_1, hstring_2); - - let mut hasher_1 = DefaultHasher::new(); - let mut hasher_2 = DefaultHasher::new(); - - hstring_1.hash(&mut hasher_1); - hstring_2.hash(&mut hasher_2); - - let h1_hash = hasher_1.finish(); - let h2_hash = hasher_2.finish(); - - assert_ne!(h1_hash, h2_hash); -} - -#[test] -fn hstring_equality_combinations() { - let h = HSTRING::from("test"); - let s = String::from("test"); - let ss: &str = "test"; - - assert_eq!(h, s); - assert_eq!(&h, s); - assert_eq!(h, &s); - assert_eq!(&h, &s); - - assert_eq!(s, h); - assert_eq!(s, &h); - assert_eq!(&s, h); - assert_eq!(&s, &h); - - assert_eq!(h, *ss); - assert_eq!(&h, *ss); - assert_eq!(h, ss); - assert_eq!(&h, ss); - - assert_eq!(*ss, h); - assert_eq!(*ss, &h); - assert_eq!(ss, h); - assert_eq!(ss, &h); -} - -#[test] -fn hstring_osstring_equality_combinations() { - let wide_data = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0xD800, 0x0069, 0x0063]; - let h = HSTRING::from_wide(wide_data).unwrap(); - use std::os::windows::prelude::OsStringExt; - let s = std::ffi::OsString::from_wide(wide_data); - let ss = s.as_os_str(); - - assert_eq!(h, s); - assert_eq!(&h, s); - assert_eq!(h, &s); - assert_eq!(&h, &s); - - assert_eq!(s, h); - assert_eq!(s, &h); - assert_eq!(&s, h); - assert_eq!(&s, &h); - - assert_eq!(h, *ss); - assert_eq!(&h, *ss); - assert_eq!(h, ss); - assert_eq!(&h, ss); - - assert_eq!(*ss, h); - assert_eq!(*ss, &h); - assert_eq!(ss, h); - assert_eq!(ss, &h); -} - -#[test] -fn hstring_compat() -> Result<()> { - unsafe { - use windows::Win32::System::WinRT::*; - let hey = HSTRING::from("Hey"); - let world = HSTRING::from("World"); - assert_eq!(WindowsCompareStringOrdinal(&hey, &world)?, -1); - - let result = WindowsConcatString(&hey, &world)?; - assert_eq!(result, "HeyWorld"); - - let result = WindowsCreateString(Some(&hey.as_wide()))?; - assert_eq!(result, "Hey"); - - let result = WindowsDuplicateString(&hey)?; - assert_eq!(result, "Hey"); - - assert_eq!(WindowsGetStringLen(&hey), 3); - assert_eq!(WindowsGetStringLen(&world), 5); - - assert_eq!(WindowsIsStringEmpty(&HSTRING::new()), true); - assert_eq!(WindowsIsStringEmpty(&HSTRING::default()), true); - assert_eq!(WindowsIsStringEmpty(&world), false); - - let mut len = 0; - let buffer = WindowsGetStringRawBuffer(&world, Some(&mut len)); - assert_eq!(len, 5); - // Adding +1 to the length of the slice to validate that it is null terminated. - assert_eq!( - std::slice::from_raw_parts(buffer.0, 6), - [87, 111, 114, 108, 100, 0] - ); - - // We need to drop to raw bindings to call the raw WindowsDeleteString function to avoid double-freeing the HSTRING, - // but this test is important as it ensures that the allocators match. - let hresult = - sys::WindowsDeleteString(std::mem::transmute_copy(&*std::mem::ManuallyDrop::new(hey))); - assert_eq!(hresult, 0); - - // An HSTRING reference a.k.a. "fast pass" string is a kind of stack-based HSTRING used by C++ callers - // to avoid the heap allocation in some cases. It's not used in Rust since it assumes a wide character - // string literal, which is inconvenient to create in Rust. Here we again use raw bindings to make one - // and thereby excercise the windows::core::HSTRING support for HSTRING reference duplication. - let mut header: sys::HSTRING_HEADER = std::mem::zeroed(); - let mut stack_hstring: sys::HSTRING = std::mem::zeroed(); - let hresult = sys::WindowsCreateStringReference( - [87, 111, 114, 108, 100, 0].as_ptr(), - 5, - &mut header, - &mut stack_hstring, - ); - assert_eq!(hresult, 0); - assert_eq!(header.length, 5); - let stack_hstring: std::mem::ManuallyDrop = std::mem::transmute(stack_hstring); - let duplicate: HSTRING = (*stack_hstring).clone(); - assert_eq!(&duplicate, &*stack_hstring); - assert_eq!(duplicate, "World"); - - let mut len = 0; - let buffer = WindowsGetStringRawBuffer(&duplicate, Some(&mut len)); - assert_eq!(len, 5); - // Adding +1 to the length of the slice to validate that it is null terminated. - assert_eq!( - std::slice::from_raw_parts(buffer.0, 6), - [87, 111, 114, 108, 100, 0] - ); - - Ok(()) - } -} - -mod sys { - windows_targets::link!("api-ms-win-core-winrt-string-l1-1-0.dll" "system" fn WindowsCreateStringReference(sourcestring: PCWSTR, length: u32, hstringheader: *mut HSTRING_HEADER, string: *mut HSTRING) -> HRESULT); - windows_targets::link!("api-ms-win-core-winrt-string-l1-1-0.dll" "system" fn WindowsDeleteString(string: HSTRING) -> HRESULT); - - pub type HRESULT = i32; - pub type HSTRING = *mut core::ffi::c_void; - pub type PCWSTR = *const u16; - - #[repr(C)] - pub struct HSTRING_HEADER { - pub flags: u32, - pub length: u32, - pub padding1: u32, - pub padding2: u32, - pub data: isize, - } -} diff --git a/crates/tests/strings/Cargo.toml b/crates/tests/strings/Cargo.toml index afa47d0b8a..9beb9eb280 100644 --- a/crates/tests/strings/Cargo.toml +++ b/crates/tests/strings/Cargo.toml @@ -10,3 +10,16 @@ doctest = false [dependencies.windows-strings] path = "../../libs/strings" + +[dependencies.windows-targets] +path = "../../libs/targets" + +[dependencies.windows] +path = "../../libs/windows" +features = [ + "Win32_Foundation", + "Win32_System_WinRT", +] + +[dev-dependencies] +helpers = { package = "test_helpers", path = "../helpers" } diff --git a/crates/tests/strings/tests/bstr.rs b/crates/tests/strings/tests/bstr.rs index f5e92ffd97..5df54f5a51 100644 --- a/crates/tests/strings/tests/bstr.rs +++ b/crates/tests/strings/tests/bstr.rs @@ -1,9 +1,62 @@ +use windows::{core::Result, Win32::Foundation::*}; use windows_strings::*; #[test] -fn bstr() -> Result<()> { - let s = BSTR::from("hello"); - assert_eq!(s.len(), 5); +fn test() { + let b: BSTR = "hello".into(); + assert_eq!(b, "hello"); +} + +#[test] +fn clone() { + let a: BSTR = "hello".into(); + assert!(!a.is_empty()); + assert!(a.len() == 5); + let b = a.clone(); + assert_eq!(a, "hello"); + assert_eq!(b, "hello"); + assert_eq!("hello", a); + + let a = BSTR::default(); + assert!(a.is_empty()); + assert!(a.len() == 0); + let b = a.clone(); + assert_eq!(a, ""); + assert_eq!(b, ""); + + let a = BSTR::new(); + assert!(a.is_empty()); + assert!(a.len() == 0); + assert_eq!(a.len(), 0); + assert_eq!(a.as_wide().len(), 0); + + let wide = &[0x68, 0x65, 0x6c, 0x6c, 0x6f]; + let a = BSTR::from_wide(wide); + assert!(!a.is_empty()); + assert!(a.len() == 5); + assert_eq!(a.as_wide().len(), 5); + assert_eq!(a.as_wide(), wide); + assert_eq!(a, "hello"); - Ok(()) + let a: BSTR = "".into(); + assert!(a.is_empty()); + assert!(a.len() == 0); + + let a: BSTR = unsafe { SysAllocStringLen(None) }; + assert!(a.is_empty()); + assert!(a.len() == 0); + + let a = BSTR::from("a"); + assert_eq!(a, String::from("a")); + assert_eq!(String::from("a"), a); +} + +#[test] +fn interop() -> Result<()> { + unsafe { + let b: BSTR = "hello".into(); + SysAddRefString(&b)?; + SysFreeString(&b); + Ok(()) + } } diff --git a/crates/tests/strings/tests/hstring.rs b/crates/tests/strings/tests/hstring.rs index 23777ad9e0..86cf6e0815 100644 --- a/crates/tests/strings/tests/hstring.rs +++ b/crates/tests/strings/tests/hstring.rs @@ -1,56 +1,311 @@ +use windows::core::Result; use windows_strings::*; #[test] -fn hstring() -> Result<()> { - let s = HSTRING::from("hello"); - assert_eq!(s.len(), 5); - assert_eq!(s.as_wide().len(), 5); +fn hstring_works() { + assert_eq!(std::mem::size_of::(), std::mem::size_of::()); + let empty = HSTRING::new(); + assert!(empty.is_empty()); + assert!(empty.is_empty()); - Ok(()) + let hello = HSTRING::from("Hello"); + assert!(!hello.is_empty()); + assert!(hello.len() == 5); + + let rust = hello.to_string(); + assert!(rust == "Hello"); + assert!(rust.len() == 5); + + let hello2 = hello.clone(); + assert!(!hello2.is_empty()); + assert!(hello2.len() == 5); + + assert!(HSTRING::from("Hello") == HSTRING::from("Hello")); + assert!(HSTRING::from("Hello") != HSTRING::from("World")); + + assert!(HSTRING::from("Hello") == "Hello"); + assert!(HSTRING::from("Hello") != "Hello "); + assert!(HSTRING::from("Hello") != "Hell"); + assert!(HSTRING::from("Hello") != "World"); + + assert!(HSTRING::from("Hello").to_string() == String::from("Hello")); +} + +#[test] +fn display_format() { + let value = HSTRING::from("Hello world"); + assert!(format!("{}", value) == "Hello world"); +} + +#[test] +fn display_invalid_format() { + let s = HSTRING::from_wide(&[ + 0xD834, 0xDD1E, 0x006d, 0x0075, 0x0073, 0xDD1E, 0x0069, 0x0063, 0xD834, + ]); + let d = format!("{}", s); + assert_eq!(d, "𝄞mus�ic�"); +} + +#[test] +fn debug_format() { + let value = HSTRING::from("Hello world"); + assert!(format!("{:?}", value) == r#""Hello world""#); +} + +#[test] +fn from_empty_string() { + let h = HSTRING::from(""); + assert!(format!("{}", h).is_empty()); +} + +#[test] +fn from_os_string_string() { + let wide_data = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0xD800, 0x0069, 0x0063]; + use std::os::windows::prelude::OsStringExt; + let o = std::ffi::OsString::from_wide(wide_data); + let h = HSTRING::from(o); + let d = HSTRING::from_wide(wide_data); + assert_eq!(h, d); } #[test] -fn hstring_builder() -> Result<()> { - // Dropping a builder is fine. - _ = HStringBuilder::new(10)?; +fn from_os_str_string() { + let wide_data = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0xD800, 0x0069, 0x0063]; + use std::os::windows::prelude::OsStringExt; + let o = std::ffi::OsString::from_wide(wide_data); + let o = o.as_os_str(); + let h = HSTRING::from(o); + let d = HSTRING::from_wide(wide_data); + assert_eq!(h, d); +} + +#[test] +fn from_path() { + let p = std::path::Path::new("/foo/bar"); + let h = HSTRING::from(p); + assert_eq!(h, "/foo/bar"); +} + +#[test] +fn hstring_to_string() { + let h = HSTRING::from("test"); + let s = String::try_from(h).unwrap(); + assert!(s == "test"); +} + +#[test] +fn hstring_to_string_err() { + // 𝄞muic + let wide_data = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0xD800, 0x0069, 0x0063]; + let h = HSTRING::from_wide(wide_data); + let err = String::try_from(h); + assert!(err.is_err()); +} + +#[test] +fn hstring_to_string_lossy() { + // 𝄞muic + let wide_data = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0xD800, 0x0069, 0x0063]; + let h = HSTRING::from_wide(wide_data); + let s = h.to_string_lossy(); + assert_eq!(s, "𝄞mu�ic"); +} + +#[test] +fn hstring_to_os_string() { + // 𝄞muic + let wide_data = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0xD800, 0x0069, 0x0063]; + let h = HSTRING::from_wide(wide_data); + let s = h.to_os_string(); + use std::os::windows::prelude::OsStringExt; + assert_eq!(s, std::ffi::OsString::from_wide(wide_data)); +} + +#[test] +fn hstring_hashing_equal_strings() { + // Checks if two strings of identical contents have the same hash + use std::hash::{DefaultHasher, Hash, Hasher}; + + let hstring_1 = HSTRING::from("Hello World"); + let hstring_2 = HSTRING::from("Hello World"); - // A zero length builder is also fine. - let b = HStringBuilder::new(0)?; - let h: HSTRING = b.into(); - assert!(h.is_empty()); + assert_eq!(hstring_1, hstring_2); - // Trimming a zero length builder is also fine. - let mut b = HStringBuilder::new(0)?; - b.trim_end(); - let h: HSTRING = b.into(); - assert!(h.is_empty()); + let mut hasher_1 = DefaultHasher::new(); + let mut hasher_2 = DefaultHasher::new(); - // This depends on DerefMut. - const HELLO: [u16; 5] = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; - let mut b = HStringBuilder::new(5)?; - b.copy_from_slice(&HELLO); - let h: HSTRING = b.into(); - assert_eq!(&h, "Hello"); + hstring_1.hash(&mut hasher_1); + hstring_2.hash(&mut hasher_2); - // HSTRING can handle embedded nulls. - const HELLO00: [u16; 7] = [0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x00, 0x00]; - let mut b = HStringBuilder::new(7)?; - b.copy_from_slice(&HELLO00); - let h: HSTRING = b.into(); - assert_eq!(h.len(), 7); - assert_eq!(h.as_wide(), HELLO00); + let h1_hash = hasher_1.finish(); + let h2_hash = hasher_2.finish(); + + assert_eq!(h1_hash, h2_hash); +} + +#[test] +fn hstring_hashing_different_strings() { + // Checks if two strings of different contents have the same hash + use std::hash::{DefaultHasher, Hash, Hasher}; + + let hstring_1 = HSTRING::from("Hello World"); + let hstring_2 = HSTRING::from("Hello World 2"); + + assert_ne!(hstring_1, hstring_2); + + let mut hasher_1 = DefaultHasher::new(); + let mut hasher_2 = DefaultHasher::new(); + + hstring_1.hash(&mut hasher_1); + hstring_2.hash(&mut hasher_2); + + let h1_hash = hasher_1.finish(); + let h2_hash = hasher_2.finish(); + + assert_ne!(h1_hash, h2_hash); +} + +#[test] +fn hstring_equality_combinations() { + let h = HSTRING::from("test"); + let s = String::from("test"); + let ss: &str = "test"; + + assert_eq!(h, s); + assert_eq!(&h, s); + assert_eq!(h, &s); + assert_eq!(&h, &s); + + assert_eq!(s, h); + assert_eq!(s, &h); + assert_eq!(&s, h); + assert_eq!(&s, &h); + + assert_eq!(h, *ss); + assert_eq!(&h, *ss); + assert_eq!(h, ss); + assert_eq!(&h, ss); + + assert_eq!(*ss, h); + assert_eq!(*ss, &h); + assert_eq!(ss, h); + assert_eq!(ss, &h); +} + +#[test] +fn hstring_osstring_equality_combinations() { + let wide_data = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0xD800, 0x0069, 0x0063]; + let h = HSTRING::from_wide(wide_data); + use std::os::windows::prelude::OsStringExt; + let s = std::ffi::OsString::from_wide(wide_data); + let ss = s.as_os_str(); + + assert_eq!(h, s); + assert_eq!(&h, s); + assert_eq!(h, &s); + assert_eq!(&h, &s); + + assert_eq!(s, h); + assert_eq!(s, &h); + assert_eq!(&s, h); + assert_eq!(&s, &h); + + assert_eq!(h, *ss); + assert_eq!(&h, *ss); + assert_eq!(h, ss); + assert_eq!(&h, ss); + + assert_eq!(*ss, h); + assert_eq!(*ss, &h); + assert_eq!(ss, h); + assert_eq!(ss, &h); +} + +#[test] +fn hstring_compat() -> Result<()> { + unsafe { + use windows::Win32::System::WinRT::*; + let hey = HSTRING::from("Hey"); + let world = HSTRING::from("World"); + assert_eq!(WindowsCompareStringOrdinal(&hey, &world)?, -1); + + let result = WindowsConcatString(&hey, &world)?; + assert_eq!(result, "HeyWorld"); + + let result = WindowsCreateString(Some(&hey.as_wide()))?; + assert_eq!(result, "Hey"); + + let result = WindowsDuplicateString(&hey)?; + assert_eq!(result, "Hey"); + + assert_eq!(WindowsGetStringLen(&hey), 3); + assert_eq!(WindowsGetStringLen(&world), 5); + + assert_eq!(WindowsIsStringEmpty(&HSTRING::new()), true); + assert_eq!(WindowsIsStringEmpty(&HSTRING::default()), true); + assert_eq!(WindowsIsStringEmpty(&world), false); + + let mut len = 0; + let buffer = WindowsGetStringRawBuffer(&world, Some(&mut len)); + assert_eq!(len, 5); + // Adding +1 to the length of the slice to validate that it is null terminated. + assert_eq!( + std::slice::from_raw_parts(buffer.0, 6), + [87, 111, 114, 108, 100, 0] + ); + + // We need to drop to raw bindings to call the raw WindowsDeleteString function to avoid double-freeing the HSTRING, + // but this test is important as it ensures that the allocators match. + let hresult = + sys::WindowsDeleteString(std::mem::transmute_copy(&*std::mem::ManuallyDrop::new(hey))); + assert_eq!(hresult, 0); + + // An HSTRING reference a.k.a. "fast pass" string is a kind of stack-based HSTRING used by C++ callers + // to avoid the heap allocation in some cases. It's not used in Rust since it assumes a wide character + // string literal, which is inconvenient to create in Rust. Here we again use raw bindings to make one + // and thereby excercise the windows::core::HSTRING support for HSTRING reference duplication. + let mut header: sys::HSTRING_HEADER = std::mem::zeroed(); + let mut stack_hstring: sys::HSTRING = std::mem::zeroed(); + let hresult = sys::WindowsCreateStringReference( + [87, 111, 114, 108, 100, 0].as_ptr(), + 5, + &mut header, + &mut stack_hstring, + ); + assert_eq!(hresult, 0); + assert_eq!(header.length, 5); + let stack_hstring: std::mem::ManuallyDrop = std::mem::transmute(stack_hstring); + let duplicate: HSTRING = (*stack_hstring).clone(); + assert_eq!(&duplicate, &*stack_hstring); + assert_eq!(duplicate, "World"); + + let mut len = 0; + let buffer = WindowsGetStringRawBuffer(&duplicate, Some(&mut len)); + assert_eq!(len, 5); + // Adding +1 to the length of the slice to validate that it is null terminated. + assert_eq!( + std::slice::from_raw_parts(buffer.0, 6), + [87, 111, 114, 108, 100, 0] + ); + + Ok(()) + } +} - // But trim_end can avoid that. - let mut b = HStringBuilder::new(7)?; - b.copy_from_slice(&HELLO00); - b.trim_end(); - let h: HSTRING = b.into(); - assert_eq!(h.len(), 5); - assert_eq!(h.as_wide(), HELLO); +mod sys { + windows_targets::link!("api-ms-win-core-winrt-string-l1-1-0.dll" "system" fn WindowsCreateStringReference(sourcestring: PCWSTR, length: u32, hstringheader: *mut HSTRING_HEADER, string: *mut HSTRING) -> HRESULT); + windows_targets::link!("api-ms-win-core-winrt-string-l1-1-0.dll" "system" fn WindowsDeleteString(string: HSTRING) -> HRESULT); - // HStringBuilder will initialize memory to zero. - let b = HStringBuilder::new(5)?; - assert_eq!(*b, [0, 0, 0, 0, 0]); + pub type HRESULT = i32; + pub type HSTRING = *mut core::ffi::c_void; + pub type PCWSTR = *const u16; - Ok(()) + #[repr(C)] + pub struct HSTRING_HEADER { + pub flags: u32, + pub length: u32, + pub padding1: u32, + pub padding2: u32, + pub data: isize, + } } diff --git a/crates/tests/strings/tests/hstring_builder.rs b/crates/tests/strings/tests/hstring_builder.rs new file mode 100644 index 0000000000..3f1ca2aa0a --- /dev/null +++ b/crates/tests/strings/tests/hstring_builder.rs @@ -0,0 +1,52 @@ +use windows_strings::*; + +#[test] +fn hstring() { + let s = HSTRING::from("hello"); + assert_eq!(s.len(), 5); + assert_eq!(s.as_wide().len(), 5); +} + +#[test] +fn hstring_builder() { + // Dropping a builder is fine. + _ = HStringBuilder::new(10); + + // A zero length builder is also fine. + let b = HStringBuilder::new(0); + let h: HSTRING = b.into(); + assert!(h.is_empty()); + + // Trimming a zero length builder is also fine. + let mut b = HStringBuilder::new(0); + b.trim_end(); + let h: HSTRING = b.into(); + assert!(h.is_empty()); + + // This depends on DerefMut. + const HELLO: [u16; 5] = [0x48, 0x65, 0x6C, 0x6C, 0x6F]; + let mut b = HStringBuilder::new(5); + b.copy_from_slice(&HELLO); + let h: HSTRING = b.into(); + assert_eq!(&h, "Hello"); + + // HSTRING can handle embedded nulls. + const HELLO00: [u16; 7] = [0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x00, 0x00]; + let mut b = HStringBuilder::new(7); + b.copy_from_slice(&HELLO00); + let h: HSTRING = b.into(); + assert_eq!(h.len(), 7); + assert_eq!(h.as_wide(), HELLO00); + + // But trim_end can avoid that. + let mut b = HStringBuilder::new(7); + b.copy_from_slice(&HELLO00); + b.trim_end(); + let h: HSTRING = b.into(); + assert_eq!(h.len(), 5); + assert_eq!(h.as_wide(), HELLO); + + // HStringBuilder will initialize memory to zero. + let b = HStringBuilder::new(5); + assert_eq!(*b, [0, 0, 0, 0, 0]); +} diff --git a/crates/tests/strings/tests/literals.rs b/crates/tests/strings/tests/literals.rs index 701e50f5ae..4d61c32615 100644 --- a/crates/tests/strings/tests/literals.rs +++ b/crates/tests/strings/tests/literals.rs @@ -1,3 +1,4 @@ +use windows::core::Result; use windows_strings::*; #[test] diff --git a/crates/tests/core/tests/pcstr.rs b/crates/tests/strings/tests/pcstr.rs similarity index 100% rename from crates/tests/core/tests/pcstr.rs rename to crates/tests/strings/tests/pcstr.rs diff --git a/crates/tests/core/tests/pcwstr.rs b/crates/tests/strings/tests/pcwstr.rs similarity index 93% rename from crates/tests/core/tests/pcwstr.rs rename to crates/tests/strings/tests/pcwstr.rs index 1ddaf5231f..1fb176065c 100644 --- a/crates/tests/core/tests/pcwstr.rs +++ b/crates/tests/strings/tests/pcwstr.rs @@ -19,7 +19,7 @@ fn test() -> Result<()> { ); let p: PCWSTR = w!("world"); - let s: HSTRING = unsafe { p.to_hstring()? }; + let s: HSTRING = unsafe { p.to_hstring() }; assert_eq!("world", s); Ok(()) diff --git a/crates/tests/core/tests/pstr.rs b/crates/tests/strings/tests/pstr.rs similarity index 100% rename from crates/tests/core/tests/pstr.rs rename to crates/tests/strings/tests/pstr.rs diff --git a/crates/tests/core/tests/pwstr.rs b/crates/tests/strings/tests/pwstr.rs similarity index 94% rename from crates/tests/core/tests/pwstr.rs rename to crates/tests/strings/tests/pwstr.rs index a442f0d6a2..126a9b2b53 100644 --- a/crates/tests/core/tests/pwstr.rs +++ b/crates/tests/strings/tests/pwstr.rs @@ -19,7 +19,7 @@ fn test() -> Result<()> { ); let p = PWSTR::from_raw(w!("world").as_ptr() as *mut _); - let s: HSTRING = unsafe { p.to_hstring()? }; + let s: HSTRING = unsafe { p.to_hstring() }; assert_eq!("world", s); Ok(()) diff --git a/crates/tools/bindings/src/core_com.txt b/crates/tools/bindings/src/core_com.txt index 362aa9e5ab..9448105e1d 100644 --- a/crates/tools/bindings/src/core_com.txt +++ b/crates/tools/bindings/src/core_com.txt @@ -8,7 +8,6 @@ Windows.Win32.Foundation.E_BOUNDS Windows.Win32.Foundation.E_INVALIDARG Windows.Win32.Foundation.E_NOINTERFACE - Windows.Win32.Foundation.E_OUTOFMEMORY Windows.Win32.Foundation.E_POINTER Windows.Win32.Foundation.JSCRIPT_E_CANTEXECUTE Windows.Win32.Foundation.RPC_E_DISCONNECTED diff --git a/crates/tools/bindings/src/strings.txt b/crates/tools/bindings/src/strings.txt index bb0f637c00..e6b83c1fe0 100644 --- a/crates/tools/bindings/src/strings.txt +++ b/crates/tools/bindings/src/strings.txt @@ -4,7 +4,6 @@ --config flatten sys minimal no-bindgen-comment --filter - Windows.Win32.Foundation.E_OUTOFMEMORY Windows.Win32.Foundation.SysAllocStringLen Windows.Win32.Foundation.SysFreeString Windows.Win32.Foundation.SysStringLen