Skip to main content

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/// A pointer to a constant null-terminated UTF-16 string.
9///
10/// Service Fabric uses UTF-16 on every supported platform. This is deliberately
11/// distinct from `windows_core::PCWSTR`, whose length operations use the
12/// platform `wcslen` even where `wchar_t` is not 16 bits.
13#[repr(transparent)]
14#[derive(Clone, Copy, PartialEq, Eq, Debug)]
15pub struct PCWSTR(pub *const u16);
16
17impl AsRef<Self> for PCWSTR {
18    fn as_ref(&self) -> &Self {
19        self
20    }
21}
22
23impl windows_core::imp::TypeKind for PCWSTR {
24    type TypeKind = windows_core::imp::CopyType;
25}
26
27impl PCWSTR {
28    /// Constructs a `PCWSTR` from a raw pointer.
29    pub const fn from_raw(ptr: *const u16) -> Self {
30        Self(ptr)
31    }
32
33    /// Constructs a null `PCWSTR`.
34    pub const fn null() -> Self {
35        Self(core::ptr::null())
36    }
37
38    /// Returns the raw pointer.
39    pub const fn as_ptr(&self) -> *const u16 {
40        self.0
41    }
42
43    /// Returns whether the pointer is null.
44    pub fn is_null(&self) -> bool {
45        self.0.is_null()
46    }
47
48    /// Returns the UTF-16 string length without the trailing null.
49    ///
50    /// # Safety
51    ///
52    /// The pointer must be valid for reads up to and including the next null `u16`.
53    pub unsafe fn len(&self) -> usize {
54        let mut len = 0;
55        let mut ptr = self.0;
56        while unsafe { ptr.read() } != 0 {
57            len += 1;
58            ptr = unsafe { ptr.add(1) };
59        }
60        len
61    }
62
63    /// Returns whether the UTF-16 string is empty.
64    ///
65    /// # Safety
66    ///
67    /// The pointer must be valid for reads up to and including the next null `u16`.
68    pub unsafe fn is_empty(&self) -> bool {
69        unsafe { self.len() == 0 }
70    }
71
72    /// Returns the UTF-16 string data without the trailing null.
73    ///
74    /// # Safety
75    ///
76    /// The pointer must be valid for reads up to and including the next null `u16`.
77    pub unsafe fn as_wide(&self) -> &[u16] {
78        unsafe { core::slice::from_raw_parts(self.0, self.len()) }
79    }
80}
81
82impl Default for PCWSTR {
83    fn default() -> Self {
84        Self::null()
85    }
86}
87
88/// WString is the utf16 string, similar to std::wstring in cpp.
89/// It is used for passing utf16 string buffers between Rust and COM.
90// The inner buffer is null terminated u16 vec.
91#[derive(Clone, PartialEq, Eq, Default, Hash)]
92pub struct WString(Option<Vec<u16>>);
93const EMPTY: [u16; 1] = [0];
94
95impl WString {
96    /// creates an empty string
97    pub const fn new() -> Self {
98        Self(None)
99    }
100
101    /// returns if the string is empty
102    pub const fn is_empty(&self) -> bool {
103        self.0.is_none()
104    }
105
106    /// len is the utf16 len not including the null terminator bytes
107    pub fn len(&self) -> usize {
108        match self.0.as_ref() {
109            Some(v) => v.len() - 1,
110            None => 0,
111        }
112    }
113
114    /// Get the string as 16-bit wide characters (wchars).
115    pub fn as_wide(&self) -> &[u16] {
116        match self.0.as_ref() {
117            Some(v) => {
118                // remove the last null terminator
119                v.as_slice().split_last().unwrap().1
120            }
121            None => &[],
122        }
123    }
124
125    /// Get the contents of this `WString` as a String lossily.
126    pub fn to_string_lossy(&self) -> String {
127        String::from_utf16_lossy(self.as_wide())
128    }
129
130    /// Returns a raw pointer to the `WString` buffer.
131    pub fn as_ptr(&self) -> *const u16 {
132        match self.0.as_ref() {
133            Some(v) => v.as_ptr(),
134            None => EMPTY.as_ptr(), // This is not null pointer.
135        }
136    }
137
138    /// Returns the `PCWSTR` representation of this `WString` for FFI calls.
139    pub fn as_pcwstr(&self) -> PCWSTR {
140        match self.0.as_ref() {
141            Some(v) => PCWSTR::from_raw(v.as_ptr()),
142            None => PCWSTR::null(),
143        }
144    }
145
146    /// From slice without the null terminator.
147    pub fn from_wide(value: &[u16]) -> Self {
148        // TODO: avoid the clone for the iter.
149        unsafe { Self::from_wide_iter(value.iter().cloned(), value.len()) }
150    }
151
152    unsafe fn from_wide_iter<I: Iterator<Item = u16>>(iter: I, len: usize) -> Self {
153        if len == 0 {
154            return Self::new();
155        }
156        // append a null terminator. collect should allocate efficiently from iter.
157        let iter = iter.chain(EMPTY.as_ref().iter().cloned());
158        let v = iter.collect::<Vec<_>>();
159        Self(Some(v))
160    }
161}
162
163impl From<&str> for WString {
164    fn from(value: &str) -> Self {
165        // TODO: if string is empty string. we should still allocate.
166        // There are 3 states: null, empty, non-empty. Currently it is not handled correctly.
167        unsafe { Self::from_wide_iter(value.encode_utf16(), value.len()) }
168    }
169}
170
171impl From<String> for WString {
172    fn from(value: String) -> Self {
173        value.as_str().into()
174    }
175}
176impl From<&String> for WString {
177    fn from(value: &String) -> Self {
178        value.as_str().into()
179    }
180}
181
182impl From<&PCWSTR> for WString {
183    /// Requires value points to valid memory location
184    /// Null is ok.
185    fn from(value: &PCWSTR) -> Self {
186        if value.is_null() {
187            Self::new()
188        } else {
189            Self::from_wide(unsafe { value.as_wide() })
190        }
191    }
192}
193
194impl From<PCWSTR> for WString {
195    fn from(value: PCWSTR) -> Self {
196        Self::from(&value)
197    }
198}
199
200/// FFI conversion.
201impl From<Option<&WString>> for PCWSTR {
202    fn from(value: Option<&WString>) -> Self {
203        value.map_or_else(Self::null, WString::as_pcwstr)
204    }
205}
206
207impl core::fmt::Display for WString {
208    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
209        // convert u16 to char gracefully and write to formatter.
210        let wit = core::char::decode_utf16(self.as_wide().iter().cloned());
211        for c in wit {
212            match c {
213                Ok(c) => f.write_char(c)?,
214                Err(_) => f.write_char(core::char::REPLACEMENT_CHARACTER)?,
215            }
216        }
217        Ok(())
218    }
219}
220
221impl core::fmt::Debug for WString {
222    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223        write!(f, "\"{self}\"")
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use crate::PCWSTR;
230
231    use super::WString;
232
233    #[test]
234    fn string_test() {
235        let test_case = |s: &str| {
236            let h = WString::from(s);
237            assert_eq!(s.len(), h.len());
238            assert_eq!(s.is_empty(), h.is_empty());
239            assert_eq!(format!("{h}"), s);
240            assert_eq!(s, h.to_string_lossy());
241            assert_eq!(h.as_wide().len(), s.len());
242            let raw = h.as_ptr();
243            let h2 = WString::from(PCWSTR::from_raw(raw));
244            assert_eq!(s, h2.to_string_lossy());
245            assert_eq!(h, h2);
246            assert_ne!(h, WString::from("dummy"));
247        };
248
249        test_case("hello");
250        test_case("s");
251        test_case("");
252    }
253
254    #[test]
255    fn pcwstr_conversion_scans_utf16_code_units() {
256        let value = WString::from("a😀ω");
257        let raw = value.as_pcwstr();
258
259        assert_eq!(unsafe { raw.as_wide() }, value.as_wide());
260        assert_eq!(WString::from(raw), value);
261    }
262
263    #[test]
264    fn pcwstr_null_conversion_is_empty() {
265        let raw = PCWSTR::null();
266
267        assert!(raw.is_null());
268        assert_eq!(WString::from(raw), WString::new());
269    }
270
271    #[test]
272    fn wide_literal_returns_pal_pcwstr() {
273        const VALUE: PCWSTR = crate::w!("a😀ω");
274
275        assert_eq!(unsafe { VALUE.as_wide() }, WString::from("a😀ω").as_wide());
276    }
277
278    #[cfg(target_os = "linux")]
279    #[test]
280    fn upstream_pcwstr_uses_linux_wchar_width() {
281        use windows_core::PCWSTR as UpstreamPCWSTR;
282
283        #[repr(align(4))]
284        struct AlignedUtf16([u16; 4]);
285
286        let value = AlignedUtf16(['a' as u16, 'b' as u16, 0, 0]);
287        let raw = UpstreamPCWSTR::from_raw(value.0.as_ptr());
288
289        assert_eq!(unsafe { raw.len() }, 1);
290        assert_eq!(unsafe { raw.as_wide() }, &value.0[..1]);
291    }
292}