Consistent allocation failure handling (#3209)

This commit is contained in:
Kenny Kerr 2024-08-20 14:42:40 -05:00 коммит произвёл GitHub
Родитель 1e662ff4d1
Коммит 75a632dc4c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
30 изменённых файлов: 464 добавлений и 499 удалений

2
.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

6
.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_standalone --target ${{ matrix.target }} ${{ matrix.etc }}
- 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: Clean
run: cargo clean
- name: Test test_structs
run: cargo test -p test_structs --target ${{ matrix.target }} ${{ matrix.etc }}
- name: Test test_sys

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

@ -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,

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

@ -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())

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

@ -111,7 +111,7 @@ impl TryFrom<Value> for HSTRING {
type Error = Error;
fn try_from(from: Value) -> Result<Self> {
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()),
}
}

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

@ -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 = []

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

@ -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");
}
```

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

@ -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;

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

@ -43,23 +43,23 @@ impl BSTR {
}
/// Create a `BSTR` from a slice of 16 bit characters (wchars).
pub fn from_wide(value: &[u16]) -> Result<Self> {
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<u16> = value.encode_utf16().collect();
Self::from_wide(&value).unwrap()
Self::from_wide(&value)
}
}

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

@ -44,7 +44,7 @@ impl HSTRING {
}
/// Create a `HSTRING` from a slice of 16 bit characters (wchars).
pub fn from_wide(value: &[u16]) -> Result<Self> {
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<I: Iterator<Item = u16>>(iter: I, len: usize) -> Result<Self> {
unsafe fn from_wide_iter<I: Iterator<Item = u16>>(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()
}
}
}

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

@ -8,14 +8,14 @@ pub struct HStringBuilder(*mut HStringHeader);
impl HStringBuilder {
/// Creates a preallocated `HSTRING` value.
pub fn new(len: usize) -> Result<Self> {
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.

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

@ -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
}
}
}

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

@ -10,9 +10,6 @@
extern crate alloc;
use alloc::string::String;
pub use windows_result::Result;
use windows_result::*;
mod bstr;
pub use bstr::*;

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

@ -70,7 +70,7 @@ impl PCWSTR {
/// # Safety
///
/// See the safety information for `PCWSTR::as_wide`.
pub unsafe fn to_hstring(&self) -> Result<HSTRING> {
pub unsafe fn to_hstring(&self) -> HSTRING {
HSTRING::from_wide(self.as_wide())
}

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

@ -67,7 +67,7 @@ impl PWSTR {
/// # Safety
///
/// See the safety information for `PWSTR::as_wide`.
pub unsafe fn to_hstring(&self) -> Result<HSTRING> {
pub unsafe fn to_hstring(&self) -> HSTRING {
HSTRING::from_wide(self.as_wide())
}

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

@ -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",
]

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

@ -1 +0,0 @@

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

@ -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(())
}
}

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

@ -1,311 +0,0 @@
use windows::core::*;
#[test]
fn hstring_works() {
assert_eq!(std::mem::size_of::<HSTRING>(), std::mem::size_of::<usize>());
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<EFBFBD>ic<EFBFBD>");
}
#[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() {
// 𝄞mu<invalid>ic
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() {
// 𝄞mu<invalid>ic
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<EFBFBD>ic");
}
#[test]
fn hstring_to_os_string() {
// 𝄞mu<invalid>ic
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<HSTRING> = 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,
}
}

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

@ -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" }

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

@ -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);
Ok(())
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");
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(())
}
}

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

@ -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::<HSTRING>(), std::mem::size_of::<usize>());
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 hstring_builder() -> Result<()> {
// Dropping a builder is fine.
_ = HStringBuilder::new(10)?;
fn display_format() {
let value = HSTRING::from("Hello world");
assert!(format!("{}", value) == "Hello world");
}
// A zero length builder is also fine.
let b = HStringBuilder::new(0)?;
let h: HSTRING = b.into();
assert!(h.is_empty());
#[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<EFBFBD>ic<EFBFBD>");
}
// 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());
#[test]
fn debug_format() {
let value = HSTRING::from("Hello world");
assert!(format!("{:?}", value) == r#""Hello world""#);
}
// 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");
#[test]
fn from_empty_string() {
let h = HSTRING::from("");
assert!(format!("{}", h).is_empty());
}
// 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);
#[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);
}
// 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);
#[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);
assert_eq!(h, d);
}
// HStringBuilder will initialize memory to zero.
let b = HStringBuilder::new(5)?;
assert_eq!(*b, [0, 0, 0, 0, 0]);
#[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() {
// 𝄞mu<invalid>ic
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() {
// 𝄞mu<invalid>ic
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<EFBFBD>ic");
}
#[test]
fn hstring_to_os_string() {
// 𝄞mu<invalid>ic
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");
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);
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<HSTRING> = 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,
}
}

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

@ -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]);
}

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

@ -1,3 +1,4 @@
use windows::core::Result;
use windows_strings::*;
#[test]

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

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

@ -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(())

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

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

@ -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(())

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

@ -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

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

@ -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