mssf_pal/
strings.rs

1// ------------------------------------------------------------
2// Copyright (c) Microsoft Corporation.  All rights reserved.
3// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
4// ------------------------------------------------------------
5
6use std::fmt::Write;
7
8#[repr(transparent)]
9#[derive(Clone, Copy, PartialEq, Eq, Debug)]
10pub struct PCWSTR(pub *const u16);
11
12impl AsRef<PCWSTR> for PCWSTR {
13    fn as_ref(&self) -> &Self {
14        self
15    }
16}
17
18impl windows_core::TypeKind for PCWSTR {
19    type TypeKind = windows_core::CopyType;
20}
21
22// Copied minimal impl from windows_core crate which is not available on linux.
23// This is used on windows as well instead of the original defs if you use mssf-pal.
24impl PCWSTR {
25    /// Construct a new `PCWSTR` from a raw pointer
26    pub const fn from_raw(ptr: *const u16) -> Self {
27        Self(ptr)
28    }
29
30    /// Construct a null `PCWSTR`
31    pub const fn null() -> Self {
32        Self(core::ptr::null())
33    }
34
35    /// Returns a raw pointer to the `PCWSTR`
36    pub const fn as_ptr(&self) -> *const u16 {
37        self.0
38    }
39
40    /// Checks whether the `PCWSTR` is null
41    pub fn is_null(&self) -> bool {
42        self.0.is_null()
43    }
44
45    /// String length without the trailing 0
46    ///
47    /// # Safety
48    ///
49    /// The `PCWSTR`'s pointer needs to be valid for reads up until and including the next `\0`.
50    pub unsafe fn len(&self) -> usize {
51        let mut len = 0;
52        let mut ptr = self.0;
53        while ptr.read() != 0 {
54            len += 1;
55            ptr = ptr.add(1);
56        }
57        len
58    }
59
60    /// Returns `true` if the string length is zero, and `false` otherwise.
61    ///
62    /// # Safety
63    ///
64    /// The `PCWSTR`'s pointer needs to be valid for reads up until and including the next `\0`.
65    pub unsafe fn is_empty(&self) -> bool {
66        self.len() == 0
67    }
68
69    /// String data without the trailing 0
70    ///
71    /// # Safety
72    ///
73    /// The `PCWSTR`'s pointer needs to be valid for reads up until and including the next `\0`.
74    pub unsafe fn as_wide(&self) -> &[u16] {
75        core::slice::from_raw_parts(self.0, self.len())
76    }
77}
78
79#[repr(transparent)]
80#[derive(Clone, Copy, PartialEq, Eq, Debug)]
81pub struct PCSTR(pub *const u8);
82
83impl AsRef<PCSTR> for PCSTR {
84    fn as_ref(&self) -> &Self {
85        self
86    }
87}
88
89impl windows_core::TypeKind for PCSTR {
90    type TypeKind = windows_core::CopyType;
91}
92
93/// WString is the utf16 string, similar to std::wstring in cpp.
94/// It is used for passing utf16 string buffers between Rust and COM.
95// The inner buffer is null terminated u16 vec.
96#[derive(Clone, PartialEq, Eq, Default)]
97pub struct WString(Option<Vec<u16>>);
98const EMPTY: [u16; 1] = [0];
99
100impl WString {
101    /// creates an empty string
102    pub const fn new() -> Self {
103        Self(None)
104    }
105
106    /// returns if the string is empty
107    pub const fn is_empty(&self) -> bool {
108        self.0.is_none()
109    }
110
111    /// len is the utf16 len not including the null terminator bytes
112    pub fn len(&self) -> usize {
113        match self.0.as_ref() {
114            Some(v) => v.len() - 1,
115            None => 0,
116        }
117    }
118
119    /// Get the string as 16-bit wide characters (wchars).
120    pub fn as_wide(&self) -> &[u16] {
121        match self.0.as_ref() {
122            Some(v) => {
123                // remove the last null terminator
124                v.as_slice().split_last().unwrap().1
125            }
126            None => &[],
127        }
128    }
129
130    /// Get the contents of this `WString` as a String lossily.
131    pub fn to_string_lossy(&self) -> String {
132        String::from_utf16_lossy(self.as_wide())
133    }
134
135    /// Returns a raw pointer to the `WString` buffer.
136    pub fn as_ptr(&self) -> *const u16 {
137        match self.0.as_ref() {
138            Some(v) => v.as_ptr(),
139            None => EMPTY.as_ptr(),
140        }
141    }
142
143    pub fn as_pcwstr(&self) -> PCWSTR {
144        PCWSTR::from_raw(self.as_ptr())
145    }
146
147    /// From slice without the null terminator.
148    pub fn from_wide(value: &[u16]) -> Self {
149        // TODO: avoid the clone for the iter.
150        unsafe { Self::from_wide_iter(value.iter().cloned(), value.len()) }
151    }
152
153    unsafe fn from_wide_iter<I: Iterator<Item = u16>>(iter: I, len: usize) -> Self {
154        if len == 0 {
155            return Self::new();
156        }
157        // append a null terminator. collect should allocate efficiently from iter.
158        let iter = iter.chain(EMPTY.as_ref().iter().cloned());
159        let v = iter.collect::<Vec<_>>();
160        Self(Some(v))
161    }
162}
163
164impl From<&str> for WString {
165    fn from(value: &str) -> Self {
166        unsafe { Self::from_wide_iter(value.encode_utf16(), value.len()) }
167    }
168}
169
170impl From<String> for WString {
171    fn from(value: String) -> Self {
172        value.as_str().into()
173    }
174}
175impl From<&String> for WString {
176    fn from(value: &String) -> Self {
177        value.as_str().into()
178    }
179}
180
181impl core::fmt::Display for WString {
182    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
183        // convert u16 to char gracefully and write to formatter.
184        let wit = core::char::decode_utf16(self.as_wide().iter().cloned());
185        for c in wit {
186            match c {
187                Ok(c) => f.write_char(c)?,
188                Err(_) => f.write_char(core::char::REPLACEMENT_CHARACTER)?,
189            }
190        }
191        Ok(())
192    }
193}
194
195impl core::fmt::Debug for WString {
196    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197        write!(f, "\"{}\"", self)
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use crate::PCWSTR;
204
205    use super::WString;
206
207    #[test]
208    fn string_test() {
209        let test_case = |s: &str| {
210            let h = WString::from(s);
211            assert_eq!(s.len(), h.len());
212            assert_eq!(s.is_empty(), h.is_empty());
213            assert_eq!(format!("{}", h), s);
214            assert_eq!(s, h.to_string_lossy());
215            assert_eq!(h.as_wide().len(), s.len());
216            let raw = h.as_ptr();
217            let h2 = WString::from_wide(unsafe { PCWSTR(raw).as_wide() });
218            assert_eq!(s, h2.to_string_lossy());
219            assert_eq!(h, h2);
220            assert_ne!(h, WString::from("dummy"));
221        };
222
223        test_case("hello");
224        test_case("s");
225        test_case("");
226    }
227}