use std::fmt::Write;
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct PCWSTR(pub *const u16);
impl AsRef<Self> for PCWSTR {
fn as_ref(&self) -> &Self {
self
}
}
impl windows_core::imp::TypeKind for PCWSTR {
type TypeKind = windows_core::imp::CopyType;
}
impl PCWSTR {
pub const fn from_raw(ptr: *const u16) -> Self {
Self(ptr)
}
pub const fn null() -> Self {
Self(core::ptr::null())
}
pub const fn as_ptr(&self) -> *const u16 {
self.0
}
pub fn is_null(&self) -> bool {
self.0.is_null()
}
pub unsafe fn len(&self) -> usize {
let mut len = 0;
let mut ptr = self.0;
while unsafe { ptr.read() } != 0 {
len += 1;
ptr = unsafe { ptr.add(1) };
}
len
}
pub unsafe fn is_empty(&self) -> bool {
unsafe { self.len() == 0 }
}
pub unsafe fn as_wide(&self) -> &[u16] {
unsafe { core::slice::from_raw_parts(self.0, self.len()) }
}
}
impl Default for PCWSTR {
fn default() -> Self {
Self::null()
}
}
#[derive(Clone, PartialEq, Eq, Default, Hash)]
pub struct WString(Option<Vec<u16>>);
const EMPTY: [u16; 1] = [0];
impl WString {
pub const fn new() -> Self {
Self(None)
}
pub const fn is_empty(&self) -> bool {
self.0.is_none()
}
pub fn len(&self) -> usize {
match self.0.as_ref() {
Some(v) => v.len() - 1,
None => 0,
}
}
pub fn as_wide(&self) -> &[u16] {
match self.0.as_ref() {
Some(v) => {
v.as_slice().split_last().unwrap().1
}
None => &[],
}
}
pub fn to_string_lossy(&self) -> String {
String::from_utf16_lossy(self.as_wide())
}
pub fn as_ptr(&self) -> *const u16 {
match self.0.as_ref() {
Some(v) => v.as_ptr(),
None => EMPTY.as_ptr(), }
}
pub fn as_pcwstr(&self) -> PCWSTR {
match self.0.as_ref() {
Some(v) => PCWSTR::from_raw(v.as_ptr()),
None => PCWSTR::null(),
}
}
pub fn from_wide(value: &[u16]) -> Self {
unsafe { Self::from_wide_iter(value.iter().cloned(), value.len()) }
}
unsafe fn from_wide_iter<I: Iterator<Item = u16>>(iter: I, len: usize) -> Self {
if len == 0 {
return Self::new();
}
let iter = iter.chain(EMPTY.as_ref().iter().cloned());
let v = iter.collect::<Vec<_>>();
Self(Some(v))
}
}
impl From<&str> for WString {
fn from(value: &str) -> Self {
unsafe { Self::from_wide_iter(value.encode_utf16(), value.len()) }
}
}
impl From<String> for WString {
fn from(value: String) -> Self {
value.as_str().into()
}
}
impl From<&String> for WString {
fn from(value: &String) -> Self {
value.as_str().into()
}
}
impl From<&PCWSTR> for WString {
fn from(value: &PCWSTR) -> Self {
if value.is_null() {
Self::new()
} else {
Self::from_wide(unsafe { value.as_wide() })
}
}
}
impl From<PCWSTR> for WString {
fn from(value: PCWSTR) -> Self {
Self::from(&value)
}
}
impl From<Option<&WString>> for PCWSTR {
fn from(value: Option<&WString>) -> Self {
value.map_or_else(Self::null, WString::as_pcwstr)
}
}
impl core::fmt::Display for WString {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let wit = core::char::decode_utf16(self.as_wide().iter().cloned());
for c in wit {
match c {
Ok(c) => f.write_char(c)?,
Err(_) => f.write_char(core::char::REPLACEMENT_CHARACTER)?,
}
}
Ok(())
}
}
impl core::fmt::Debug for WString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"{self}\"")
}
}
#[cfg(test)]
mod tests {
use crate::PCWSTR;
use super::WString;
#[test]
fn string_test() {
let test_case = |s: &str| {
let h = WString::from(s);
assert_eq!(s.len(), h.len());
assert_eq!(s.is_empty(), h.is_empty());
assert_eq!(format!("{h}"), s);
assert_eq!(s, h.to_string_lossy());
assert_eq!(h.as_wide().len(), s.len());
let raw = h.as_ptr();
let h2 = WString::from(PCWSTR::from_raw(raw));
assert_eq!(s, h2.to_string_lossy());
assert_eq!(h, h2);
assert_ne!(h, WString::from("dummy"));
};
test_case("hello");
test_case("s");
test_case("");
}
#[test]
fn pcwstr_conversion_scans_utf16_code_units() {
let value = WString::from("a😀ω");
let raw = value.as_pcwstr();
assert_eq!(unsafe { raw.as_wide() }, value.as_wide());
assert_eq!(WString::from(raw), value);
}
#[test]
fn pcwstr_null_conversion_is_empty() {
let raw = PCWSTR::null();
assert!(raw.is_null());
assert_eq!(WString::from(raw), WString::new());
}
#[test]
fn wide_literal_returns_pal_pcwstr() {
const VALUE: PCWSTR = crate::w!("a😀ω");
assert_eq!(unsafe { VALUE.as_wide() }, WString::from("a😀ω").as_wide());
}
#[cfg(target_os = "linux")]
#[test]
fn upstream_pcwstr_uses_linux_wchar_width() {
use windows_core::PCWSTR as UpstreamPCWSTR;
#[repr(align(4))]
struct AlignedUtf16([u16; 4]);
let value = AlignedUtf16(['a' as u16, 'b' as u16, 0, 0]);
let raw = UpstreamPCWSTR::from_raw(value.0.as_ptr());
assert_eq!(unsafe { raw.len() }, 1);
assert_eq!(unsafe { raw.as_wide() }, &value.0[..1]);
}
}