Add `Deref` implementation for `HSTRING` (#3291)

This commit is contained in:
Kenny Kerr 2024-09-23 18:01:22 -05:00 коммит произвёл GitHub
Родитель bca9a76f40
Коммит 0f7466c34e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
10 изменённых файлов: 134 добавлений и 133 удалений

Просмотреть файл

@ -1,36 +1,26 @@
use super::*;
use core::ops::Deref;
#[repr(transparent)]
pub struct BasicString(*const u16);
impl BasicString {
pub fn is_empty(&self) -> bool {
self.len() == 0
}
impl Deref for BasicString {
type Target = [u16];
pub fn len(&self) -> usize {
if self.0.is_null() {
fn deref(&self) -> &[u16] {
let len = if self.0.is_null() {
0
} else {
unsafe { SysStringLen(self.0) as usize }
}
}
};
pub fn as_wide(&self) -> &[u16] {
let len = self.len();
if len != 0 {
unsafe { core::slice::from_raw_parts(self.as_ptr(), len) }
} else {
&[]
}
}
pub fn as_ptr(&self) -> *const u16 {
if !self.is_empty() {
self.0
if len > 0 {
unsafe { core::slice::from_raw_parts(self.0, len) }
} else {
// This ensures that if `as_ptr` is called on the slice that the resulting pointer
// will still refer to a null-terminated string.
const EMPTY: [u16; 1] = [0];
EMPTY.as_ptr()
&EMPTY[..0]
}
}
}

Просмотреть файл

@ -343,7 +343,7 @@ mod error_info {
}
}
Some(String::from_utf16_lossy(wide_trim_end(message.as_wide())))
Some(String::from_utf16_lossy(wide_trim_end(&message)))
}
pub(crate) fn as_ptr(&self) -> *mut core::ffi::c_void {

Просмотреть файл

@ -1,4 +1,5 @@
use super::*;
use core::ops::Deref;
/// A BSTR string ([BSTR](https://learn.microsoft.com/en-us/previous-versions/windows/desktop/automat/string-manipulation-functions))
/// is a length-prefixed wide string.
@ -13,35 +14,6 @@ impl BSTR {
Self(core::ptr::null_mut())
}
/// Returns `true` if the string is empty.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns the length of the string.
pub fn len(&self) -> usize {
if self.0.is_null() {
0
} else {
unsafe { bindings::SysStringLen(self.0) as usize }
}
}
/// Get the string as 16-bit wide characters (wchars).
pub fn as_wide(&self) -> &[u16] {
unsafe { core::slice::from_raw_parts(self.as_ptr(), self.len()) }
}
/// Returns a raw pointer to the `BSTR` buffer.
pub fn as_ptr(&self) -> *const u16 {
if !self.is_empty() {
self.0
} else {
const EMPTY: [u16; 1] = [0];
EMPTY.as_ptr()
}
}
/// Create a `BSTR` from a slice of 16 bit characters (wchars).
pub fn from_wide(value: &[u16]) -> Self {
if value.is_empty() {
@ -75,9 +47,30 @@ impl BSTR {
}
}
impl Deref for BSTR {
type Target = [u16];
fn deref(&self) -> &[u16] {
let len = if self.0.is_null() {
0
} else {
unsafe { bindings::SysStringLen(self.0) as usize }
};
if len > 0 {
unsafe { core::slice::from_raw_parts(self.0, len) }
} else {
// This ensures that if `as_ptr` is called on the slice that the resulting pointer
// will still refer to a null-terminated string.
const EMPTY: [u16; 1] = [0];
&EMPTY[..0]
}
}
}
impl Clone for BSTR {
fn clone(&self) -> Self {
Self::from_wide(self.as_wide())
Self::from_wide(self)
}
}
@ -104,7 +97,7 @@ impl<'a> TryFrom<&'a BSTR> for String {
type Error = alloc::string::FromUtf16Error;
fn try_from(value: &BSTR) -> core::result::Result<Self, Self::Error> {
String::from_utf16(value.as_wide())
String::from_utf16(value)
}
}
@ -127,7 +120,7 @@ impl core::fmt::Display for BSTR {
core::write!(
f,
"{}",
Decode(|| core::char::decode_utf16(self.as_wide().iter().cloned()))
Decode(|| core::char::decode_utf16(self.iter().cloned()))
)
}
}
@ -140,7 +133,7 @@ impl core::fmt::Debug for BSTR {
impl PartialEq for BSTR {
fn eq(&self, other: &Self) -> bool {
self.as_wide() == other.as_wide()
self.deref() == other.deref()
}
}
@ -160,10 +153,7 @@ impl PartialEq<BSTR> for String {
impl<T: AsRef<str> + ?Sized> PartialEq<T> for BSTR {
fn eq(&self, other: &T) -> bool {
self.as_wide()
.iter()
.copied()
.eq(other.as_ref().encode_utf16())
self.iter().copied().eq(other.as_ref().encode_utf16())
}
}

Просмотреть файл

@ -1,4 +1,5 @@
use super::*;
use core::ops::Deref;
/// An ([HSTRING](https://docs.microsoft.com/en-us/windows/win32/winrt/hstring))
/// is a reference-counted and immutable UTF-16 string type.
@ -13,36 +14,6 @@ impl HSTRING {
Self(core::ptr::null_mut())
}
/// Returns `true` if the string is empty.
pub fn is_empty(&self) -> bool {
// An empty HSTRING is represented by a null pointer.
self.0.is_null()
}
/// Returns the length of the string. The length is measured in `u16`s (UTF-16 code units), not including the terminating null character.
pub fn len(&self) -> usize {
if let Some(header) = self.as_header() {
header.len as usize
} else {
0
}
}
/// Get the string as 16-bit wide characters (wchars).
pub fn as_wide(&self) -> &[u16] {
unsafe { core::slice::from_raw_parts(self.as_ptr(), self.len()) }
}
/// Returns a raw pointer to the `HSTRING` buffer.
pub fn as_ptr(&self) -> *const u16 {
if let Some(header) = self.as_header() {
header.data
} else {
const EMPTY: [u16; 1] = [0];
EMPTY.as_ptr()
}
}
/// Create a `HSTRING` from a slice of 16 bit characters (wchars).
pub fn from_wide(value: &[u16]) -> Self {
unsafe { Self::from_wide_iter(value.iter().copied(), value.len()) }
@ -50,13 +21,13 @@ impl HSTRING {
/// Get the contents of this `HSTRING` as a String lossily.
pub fn to_string_lossy(&self) -> String {
String::from_utf16_lossy(self.as_wide())
String::from_utf16_lossy(self)
}
/// Get the contents of this `HSTRING` as a OsString.
#[cfg(feature = "std")]
pub fn to_os_string(&self) -> std::ffi::OsString {
std::os::windows::ffi::OsStringExt::from_wide(self.as_wide())
std::os::windows::ffi::OsStringExt::from_wide(self)
}
/// # Safety
@ -87,6 +58,21 @@ impl HSTRING {
}
}
impl Deref for HSTRING {
type Target = [u16];
fn deref(&self) -> &[u16] {
if let Some(header) = self.as_header() {
unsafe { core::slice::from_raw_parts(header.data, header.len as usize) }
} else {
// This ensures that if `as_ptr` is called on the slice that the resulting pointer
// will still refer to a null-terminated string.
const EMPTY: [u16; 1] = [0];
&EMPTY[..0]
}
}
}
impl Default for HSTRING {
fn default() -> Self {
Self::new()
@ -125,7 +111,7 @@ impl core::fmt::Display for HSTRING {
write!(
f,
"{}",
Decode(|| core::char::decode_utf16(self.as_wide().iter().cloned()))
Decode(|| core::char::decode_utf16(self.iter().cloned()))
)
}
}
@ -191,13 +177,13 @@ impl Eq for HSTRING {}
impl Ord for HSTRING {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.as_wide().cmp(other.as_wide())
self.deref().cmp(other)
}
}
impl core::hash::Hash for HSTRING {
fn hash<H: core::hash::Hasher>(&self, hasher: &mut H) {
self.as_wide().hash(hasher)
self.deref().hash(hasher)
}
}
@ -209,7 +195,7 @@ impl PartialOrd for HSTRING {
impl PartialEq for HSTRING {
fn eq(&self, other: &Self) -> bool {
*self.as_wide() == *other.as_wide()
self.deref() == other.deref()
}
}
@ -233,7 +219,7 @@ impl PartialEq<&String> for HSTRING {
impl PartialEq<str> for HSTRING {
fn eq(&self, other: &str) -> bool {
self.as_wide().iter().copied().eq(other.encode_utf16())
self.iter().copied().eq(other.encode_utf16())
}
}
@ -309,8 +295,7 @@ impl PartialEq<&std::ffi::OsString> for HSTRING {
#[cfg(feature = "std")]
impl PartialEq<std::ffi::OsStr> for HSTRING {
fn eq(&self, other: &std::ffi::OsStr) -> bool {
self.as_wide()
.iter()
self.iter()
.copied()
.eq(std::os::windows::ffi::OsStrExt::encode_wide(other))
}
@ -376,7 +361,7 @@ impl<'a> TryFrom<&'a HSTRING> for String {
type Error = alloc::string::FromUtf16Error;
fn try_from(hstring: &HSTRING) -> core::result::Result<Self, Self::Error> {
String::from_utf16(hstring.as_wide())
String::from_utf16(hstring)
}
}

Просмотреть файл

@ -47,8 +47,6 @@ fn test() {
fn into() {
let a = h!("");
assert!(a.is_empty());
assert!(!a.as_ptr().is_null());
assert!(a.as_wide().is_empty());
let b = PCWSTR(a.as_ptr());
// Even though an empty HSTRING is internally represented by a null pointer, the PCWSTR
// will still be a non-null pointer to a null terminated empty string.
@ -80,7 +78,7 @@ fn assert_hstring(left: &HSTRING, right: &[u16]) {
unsafe { wcslen(PCWSTR::from_raw(left.as_ptr())) },
right.len() - 1
);
let left = unsafe { std::slice::from_raw_parts(left.as_wide().as_ptr(), right.len()) };
let left = unsafe { std::slice::from_raw_parts(left.as_ptr(), right.len()) };
assert_eq!(left, right);
}

Просмотреть файл

@ -1,20 +1,4 @@
use windows::{core::*, Win32::Foundation::*, Win32::UI::Shell::*};
#[test]
fn error() {
unsafe {
SetLastError(ERROR_BUSY_DRIVE);
let utf8 = "test\0".as_bytes();
let utf16 = HSTRING::from("test\0");
let utf16 = utf16.as_wide();
let len = 5;
assert_eq!(utf8.len(), len);
assert_eq!(utf16.len(), len);
assert_eq!(GetLastError(), ERROR_BUSY_DRIVE);
}
}
use windows::{core::*, Win32::UI::Shell::*};
#[test]
fn convert() {

Просмотреть файл

@ -28,14 +28,14 @@ fn clone() {
assert!(a.is_empty());
assert!(a.len() == 0);
assert_eq!(a.len(), 0);
assert_eq!(a.as_wide().len(), 0);
assert_eq!(a.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.len(), 5);
assert_eq!(*a, *wide);
assert_eq!(a, "hello");
let a: BSTR = "".into();
@ -60,3 +60,32 @@ fn interop() -> Result<()> {
Ok(())
}
}
#[test]
fn deref_as_slice() {
let deref = BSTR::from("0123456789");
assert!(!deref.is_empty());
assert_eq!(deref.len(), 10);
assert_eq!(BSTR::from_wide(&deref[..=3]), "0123");
assert!(deref.ends_with(&deref[7..=9]));
assert_eq!(deref.get(5), Some(b'5' as u16).as_ref());
let ptr = PCWSTR(deref.as_ptr());
assert_eq!(deref.cmp(&deref), std::cmp::Ordering::Equal);
unsafe {
assert_eq!(*ptr.as_wide(), *deref);
}
let empty = BSTR::new();
assert!(empty.is_empty());
assert_eq!(empty.len(), 0);
assert_eq!(*empty, []);
unsafe {
assert_eq!(wcslen(empty.as_ptr()), 0);
}
}
extern "C" {
pub fn wcslen(s: *const u16) -> usize;
}

Просмотреть файл

@ -232,7 +232,7 @@ fn hstring_compat() -> Result<()> {
let result = WindowsConcatString(&hey, &world)?;
assert_eq!(result, "HeyWorld");
let result = WindowsCreateString(Some(&hey.as_wide()))?;
let result = WindowsCreateString(Some(&hey))?;
assert_eq!(result, "Hey");
let result = WindowsDuplicateString(&hey)?;
@ -292,6 +292,35 @@ fn hstring_compat() -> Result<()> {
}
}
#[test]
fn deref_as_slice() {
let deref = HSTRING::from("0123456789");
assert!(!deref.is_empty());
assert_eq!(deref.len(), 10);
assert_eq!(HSTRING::from_wide(&deref[..=3]), "0123");
assert!(deref.ends_with(&deref[7..=9]));
assert_eq!(deref.get(5), Some(b'5' as u16).as_ref());
let ptr = PCWSTR(deref.as_ptr());
assert_eq!(deref.cmp(&deref), std::cmp::Ordering::Equal);
unsafe {
assert_eq!(*ptr.as_wide(), *deref);
}
let empty = HSTRING::new();
assert!(empty.is_empty());
assert_eq!(empty.len(), 0);
assert_eq!(*empty, []);
unsafe {
assert_eq!(wcslen(empty.as_ptr()), 0);
}
}
extern "C" {
pub fn wcslen(s: *const u16) -> usize;
}
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);

Просмотреть файл

@ -4,7 +4,6 @@ use windows_strings::*;
fn hstring() {
let s = HSTRING::from("hello");
assert_eq!(s.len(), 5);
assert_eq!(s.as_wide().len(), 5);
}
#[test]
@ -36,7 +35,7 @@ fn hstring_builder() {
b.copy_from_slice(&HELLO00);
let h: HSTRING = b.into();
assert_eq!(h.len(), 7);
assert_eq!(h.as_wide(), HELLO00);
assert_eq!(*h, HELLO00);
// But trim_end can avoid that.
let mut b = HStringBuilder::new(7);
@ -44,7 +43,7 @@ fn hstring_builder() {
b.trim_end();
let h: HSTRING = b.into();
assert_eq!(h.len(), 5);
assert_eq!(h.as_wide(), HELLO);
assert_eq!(*h, HELLO);
// HStringBuilder will initialize memory to zero.
let b = HStringBuilder::new(5);

Просмотреть файл

@ -117,13 +117,10 @@ fn lite() -> Result<()> {
let writer = writer.unwrap();
writer.SetOutput(&stream)?;
writer.WriteStartElement(HSTRING::from("html").as_wide())?;
writer.WriteAttributeString(HSTRING::from("no-value").as_wide(), None)?;
writer.WriteAttributeString(
HSTRING::from("with-value").as_wide(),
Some(HSTRING::from("value").as_wide()),
)?;
writer.WriteEndElement(HSTRING::from("html").as_wide())?;
writer.WriteStartElement(&HSTRING::from("html"))?;
writer.WriteAttributeString(&HSTRING::from("no-value"), None)?;
writer.WriteAttributeString(&HSTRING::from("with-value"), Some(&HSTRING::from("value")))?;
writer.WriteEndElement(&HSTRING::from("html"))?;
writer.Flush()?;
let mut pos = 0;