Skip to content

Commit 62ccc94

Browse files
authored
Add HSTRING builder and registry support (#3133)
1 parent 66f1850 commit 62ccc94

File tree

15 files changed

+321
-121
lines changed

15 files changed

+321
-121
lines changed

crates/libs/registry/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@ path = "../targets"
2424
[dependencies.windows-result]
2525
version = "0.1.1"
2626
path = "../result"
27+
28+
[dependencies.windows-strings]
29+
version = "0.1.0"
30+
path = "../strings"

crates/libs/registry/src/key.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,15 @@ impl Key {
9898
unsafe { self.set_value(name, REG_SZ, value.as_ptr() as _, value.len() * 2) }
9999
}
100100

101+
/// Sets the name and value in the registry key.
102+
pub fn set_hstring<T: AsRef<str>>(
103+
&self,
104+
name: T,
105+
value: &windows_strings::HSTRING,
106+
) -> Result<()> {
107+
unsafe { self.set_value(name, REG_SZ, value.as_ptr() as _, value.len() * 2) }
108+
}
109+
101110
/// Sets the name and value in the registry key.
102111
pub fn set_multi_string<T: AsRef<str>>(&self, name: T, value: &[T]) -> Result<()> {
103112
let mut packed = value.iter().fold(vec![0u16; 0], |mut packed, value| {
@@ -278,6 +287,40 @@ impl Key {
278287
}
279288
}
280289

290+
/// Gets the value for the name in the registry key.
291+
pub fn get_hstring<T: AsRef<str>>(&self, name: T) -> Result<HSTRING> {
292+
let name = pcwstr(name);
293+
let mut ty = 0;
294+
let mut len = 0;
295+
296+
let result = unsafe {
297+
RegQueryValueExW(self.0, name.as_ptr(), null(), &mut ty, null_mut(), &mut len)
298+
};
299+
300+
win32_error(result)?;
301+
302+
if !matches!(ty, REG_SZ | REG_EXPAND_SZ) {
303+
return Err(invalid_data());
304+
}
305+
306+
let mut value = HStringBuilder::new(len as usize / 2)?;
307+
308+
let result = unsafe {
309+
RegQueryValueExW(
310+
self.0,
311+
name.as_ptr(),
312+
null(),
313+
null_mut(),
314+
value.as_mut_ptr() as _,
315+
&mut len,
316+
)
317+
};
318+
319+
win32_error(result)?;
320+
value.trim_end();
321+
Ok(value.into())
322+
}
323+
281324
/// Gets the value for the name in the registry key.
282325
pub fn get_bytes<T: AsRef<str>>(&self, name: T) -> Result<Vec<u8>> {
283326
let name = pcwstr(name);

crates/libs/registry/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ pub use r#type::Type;
3131
pub use windows_result::Result;
3232
use windows_result::*;
3333

34+
pub use windows_strings::HSTRING;
35+
use windows_strings::*;
36+
3437
/// The predefined `HKEY_CLASSES_ROOT` registry key.
3538
pub const CLASSES_ROOT: &Key = &Key(HKEY_CLASSES_ROOT);
3639

crates/libs/result/src/bindings.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ pub type BOOL = i32;
1919
pub type BSTR = *const u16;
2020
pub const ERROR_INVALID_DATA: WIN32_ERROR = 13u32;
2121
pub const ERROR_NO_UNICODE_TRANSLATION: WIN32_ERROR = 1113u32;
22-
pub const E_INVALIDARG: HRESULT = 0x80070057_u32 as _;
2322
pub const E_UNEXPECTED: HRESULT = 0x8000FFFF_u32 as _;
2423
pub const FORMAT_MESSAGE_ALLOCATE_BUFFER: FORMAT_MESSAGE_OPTIONS = 256u32;
2524
pub const FORMAT_MESSAGE_FROM_HMODULE: FORMAT_MESSAGE_OPTIONS = 2048u32;

crates/libs/strings/.natvis

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
33
<Type Name="windows_strings::hstring::HSTRING">
4-
<Intrinsic Name="header" Expression="(windows_strings::hstring::Header *)__0.tag" />
5-
<Intrinsic Name="is_empty" Expression="__0.tag == 0" />
4+
<Intrinsic Name="header" Expression="(windows_strings::hstring_header::HStringHeader *)__0" />
5+
<Intrinsic Name="is_empty" Expression="__0 == 0" />
66
<DisplayString Condition="is_empty()">""</DisplayString>
77
<DisplayString>{header()->data,[header()->len]su}</DisplayString>
88

crates/libs/strings/src/heap.rs

Lines changed: 0 additions & 40 deletions
This file was deleted.

crates/libs/strings/src/hstring.rs

Lines changed: 18 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
use super::*;
22

3-
/// A WinRT string ([HSTRING](https://docs.microsoft.com/en-us/windows/win32/winrt/hstring))
4-
/// is reference-counted and immutable.
3+
/// An ([HSTRING](https://docs.microsoft.com/en-us/windows/win32/winrt/hstring))
4+
/// is a reference-counted and immutable UTF-16 string type.
55
#[repr(transparent)]
6-
pub struct HSTRING(Option<core::ptr::NonNull<Header>>);
6+
pub struct HSTRING(pub(crate) *mut HStringHeader);
77

88
impl HSTRING {
99
/// Create an empty `HSTRING`.
1010
///
1111
/// This function does not allocate memory.
1212
pub const fn new() -> Self {
13-
Self(None)
13+
Self(core::ptr::null_mut())
1414
}
1515

1616
/// Returns `true` if the string is empty.
17-
pub const fn is_empty(&self) -> bool {
17+
pub fn is_empty(&self) -> bool {
1818
// An empty HSTRING is represented by a null pointer.
19-
self.0.is_none()
19+
self.0.is_null()
2020
}
2121

2222
/// Returns the length of the string. The length is measured in `u16`s (UTF-16 code units), not including the terminating null character.
2323
pub fn len(&self) -> usize {
24-
if let Some(header) = self.get_header() {
24+
if let Some(header) = self.as_header() {
2525
header.len as usize
2626
} else {
2727
0
@@ -35,7 +35,7 @@ impl HSTRING {
3535

3636
/// Returns a raw pointer to the `HSTRING` buffer.
3737
pub fn as_ptr(&self) -> *const u16 {
38-
if let Some(header) = self.get_header() {
38+
if let Some(header) = self.as_header() {
3939
header.data
4040
} else {
4141
const EMPTY: [u16; 1] = [0];
@@ -66,7 +66,7 @@ impl HSTRING {
6666
return Ok(Self::new());
6767
}
6868

69-
let ptr = Header::alloc(len.try_into()?)?;
69+
let ptr = HStringHeader::alloc(len.try_into()?)?;
7070

7171
// Place each utf-16 character into the buffer and
7272
// increase len as we go along.
@@ -79,11 +79,11 @@ impl HSTRING {
7979

8080
// Write a 0 byte to the end of the buffer.
8181
(*ptr).data.offset((*ptr).len as isize).write(0);
82-
Ok(Self(core::ptr::NonNull::new(ptr)))
82+
Ok(Self(ptr))
8383
}
8484

85-
fn get_header(&self) -> Option<&Header> {
86-
self.0.map(|header| unsafe { header.as_ref() })
85+
fn as_header(&self) -> Option<&HStringHeader> {
86+
unsafe { self.0.as_ref() }
8787
}
8888
}
8989

@@ -95,8 +95,8 @@ impl Default for HSTRING {
9595

9696
impl Clone for HSTRING {
9797
fn clone(&self) -> Self {
98-
if let Some(header) = self.get_header() {
99-
Self(core::ptr::NonNull::new(header.duplicate().unwrap()))
98+
if let Some(header) = self.as_header() {
99+
Self(header.duplicate().unwrap())
100100
} else {
101101
Self::new()
102102
}
@@ -105,17 +105,12 @@ impl Clone for HSTRING {
105105

106106
impl Drop for HSTRING {
107107
fn drop(&mut self) {
108-
if self.is_empty() {
109-
return;
110-
}
111-
112-
if let Some(header) = self.0.take() {
113-
// REFERENCE_FLAG indicates a string backed by static or stack memory that is
108+
if let Some(header) = self.as_header() {
109+
// HSTRING_REFERENCE_FLAG indicates a string backed by static or stack memory that is
114110
// thus not reference-counted and does not need to be freed.
115111
unsafe {
116-
let header = header.as_ref();
117-
if header.flags & REFERENCE_FLAG == 0 && header.count.release() == 0 {
118-
heap_free(header as *const _ as *mut _);
112+
if header.flags & HSTRING_REFERENCE_FLAG == 0 && header.count.release() == 0 {
113+
HStringHeader::free(self.0);
119114
}
120115
}
121116
}
@@ -407,54 +402,3 @@ impl From<HSTRING> for std::ffi::OsString {
407402
Self::from(&hstring)
408403
}
409404
}
410-
411-
const REFERENCE_FLAG: u32 = 1;
412-
413-
#[repr(C)]
414-
struct Header {
415-
flags: u32,
416-
len: u32,
417-
_0: u32,
418-
_1: u32,
419-
data: *mut u16,
420-
count: RefCount,
421-
buffer_start: u16,
422-
}
423-
424-
impl Header {
425-
fn alloc(len: u32) -> Result<*mut Header> {
426-
debug_assert!(len != 0);
427-
// Allocate enough space for header and two bytes per character.
428-
// The space for the terminating null character is already accounted for inside of `Header`.
429-
let alloc_size = core::mem::size_of::<Header>() + 2 * len as usize;
430-
431-
let header = heap_alloc(alloc_size)? as *mut Header;
432-
433-
unsafe {
434-
// Use `ptr::write` (since `header` is unintialized). `Header` is safe to be all zeros.
435-
header.write(core::mem::MaybeUninit::<Header>::zeroed().assume_init());
436-
(*header).len = len;
437-
(*header).count = RefCount::new(1);
438-
(*header).data = &mut (*header).buffer_start;
439-
}
440-
441-
Ok(header)
442-
}
443-
444-
fn duplicate(&self) -> Result<*mut Header> {
445-
if self.flags & REFERENCE_FLAG == 0 {
446-
// If this is not a "fast pass" string then simply increment the reference count.
447-
self.count.add_ref();
448-
Ok(self as *const Header as *mut Header)
449-
} else {
450-
// Otherwise, allocate a new string and copy the value into the new string.
451-
let copy = Header::alloc(self.len)?;
452-
// SAFETY: since we are duplicating the string it is safe to copy all data from self to the initialized `copy`.
453-
// We copy `len + 1` characters since `len` does not account for the terminating null character.
454-
unsafe {
455-
core::ptr::copy_nonoverlapping(self.data, (*copy).data, self.len as usize + 1);
456-
}
457-
Ok(copy)
458-
}
459-
}
460-
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use super::*;
2+
3+
/// An [HSTRING] builder that supports preallocating the `HSTRING` to avoid extra allocations and copies.
4+
///
5+
/// This is similar to the `WindowsPreallocateStringBuffer` function but implemented directly in Rust for efficiency.
6+
/// It is implemented as a separate type since [HSTRING] values are immutable.
7+
pub struct HStringBuilder(*mut HStringHeader);
8+
9+
impl HStringBuilder {
10+
/// Creates a preallocated `HSTRING` value.
11+
pub fn new(len: usize) -> Result<Self> {
12+
Ok(Self(HStringHeader::alloc(len.try_into()?)?))
13+
}
14+
15+
/// Shortens the string by removing any trailing 0 characters.
16+
pub fn trim_end(&mut self) {
17+
if let Some(header) = self.as_header_mut() {
18+
while header.len > 0
19+
&& unsafe { header.data.offset(header.len as isize - 1).read() == 0 }
20+
{
21+
header.len -= 1;
22+
}
23+
24+
if header.len == 0 {
25+
unsafe {
26+
HStringHeader::free(self.0);
27+
}
28+
self.0 = core::ptr::null_mut();
29+
}
30+
}
31+
}
32+
33+
fn as_header(&self) -> Option<&HStringHeader> {
34+
unsafe { self.0.as_ref() }
35+
}
36+
37+
fn as_header_mut(&mut self) -> Option<&mut HStringHeader> {
38+
unsafe { self.0.as_mut() }
39+
}
40+
}
41+
42+
impl From<HStringBuilder> for HSTRING {
43+
fn from(value: HStringBuilder) -> Self {
44+
if let Some(header) = value.as_header() {
45+
unsafe { header.data.offset(header.len as isize).write(0) };
46+
let result = Self(value.0);
47+
core::mem::forget(value);
48+
result
49+
} else {
50+
Self::new()
51+
}
52+
}
53+
}
54+
55+
impl core::ops::Deref for HStringBuilder {
56+
type Target = [u16];
57+
58+
fn deref(&self) -> &[u16] {
59+
if let Some(header) = self.as_header() {
60+
unsafe { core::slice::from_raw_parts(header.data, header.len as usize) }
61+
} else {
62+
&[]
63+
}
64+
}
65+
}
66+
67+
impl core::ops::DerefMut for HStringBuilder {
68+
fn deref_mut(&mut self) -> &mut [u16] {
69+
if let Some(header) = self.as_header() {
70+
unsafe { core::slice::from_raw_parts_mut(header.data, header.len as usize) }
71+
} else {
72+
&mut []
73+
}
74+
}
75+
}
76+
77+
impl Drop for HStringBuilder {
78+
fn drop(&mut self) {
79+
unsafe {
80+
HStringHeader::free(self.0);
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)