use windows::Win32::Foundation::VARIANT_BOOL;
use windows::Win32::System::Com::IDispatch;
use windows::Win32::System::Variant::{
VARIANT, VT_BOOL, VT_BSTR, VT_DISPATCH, VT_EMPTY, VT_I4, VT_I8, VT_NULL, VT_R8, VT_UI8,
VT_UNKNOWN, VariantClear,
};
use windows_core::{BSTR, Interface as _};
use crate::dispatch::ComDispatch;
use crate::error::ComError;
use crate::value::Value;
#[repr(transparent)]
pub(crate) struct OwnedVariant(VARIANT);
impl std::fmt::Debug for OwnedVariant {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let variant_type = unsafe { self.0.Anonymous.Anonymous.vt };
formatter
.debug_struct("OwnedVariant")
.field("vt", &variant_type.0)
.finish()
}
}
impl OwnedVariant {
#[must_use]
pub(crate) fn empty() -> Self {
Self(VARIANT::default())
}
pub(crate) fn from_value(value: &Value) -> Result<Self, ComError> {
let mut owned = Self::empty();
unsafe {
let inner = &mut owned.0.Anonymous.Anonymous;
match value {
Value::Empty => inner.vt = VT_EMPTY,
Value::Null => inner.vt = VT_NULL,
Value::NullObject => {
inner.vt = VT_DISPATCH;
inner.Anonymous.pdispVal = std::mem::ManuallyDrop::new(None);
}
Value::Bool(flag) => {
inner.vt = VT_BOOL;
inner.Anonymous.boolVal = VARIANT_BOOL(if *flag { -1 } else { 0 });
}
Value::I32(number) => {
inner.vt = VT_I4;
inner.Anonymous.lVal = *number;
}
Value::I64(number) => {
inner.vt = VT_I8;
inner.Anonymous.llVal = *number;
}
Value::U64(number) => {
inner.vt = VT_UI8;
inner.Anonymous.ullVal = *number;
}
Value::F64(number) => {
inner.vt = VT_R8;
inner.Anonymous.dblVal = *number;
}
Value::Str(text) => {
inner.vt = VT_BSTR;
inner.Anonymous.bstrVal = std::mem::ManuallyDrop::new(BSTR::from(text));
}
Value::Object(object) => {
let Some(dispatch) = object.as_idispatch() else {
return Err(ComError::UnexpectedType {
expected: "live COM dispatch object",
actual: "fake dispatch object",
});
};
inner.vt = VT_DISPATCH;
inner.Anonymous.pdispVal = std::mem::ManuallyDrop::new(Some(dispatch.clone()));
}
}
}
Ok(owned)
}
pub(crate) fn to_value(&self) -> Result<Value, ComError> {
unsafe {
let inner = &self.0.Anonymous.Anonymous;
let slot = &inner.Anonymous;
let value = match inner.vt {
VT_EMPTY => Value::Empty,
VT_NULL => Value::Null,
VT_BOOL => Value::Bool(slot.boolVal.0 != 0),
VT_I4 => Value::I32(slot.lVal),
VT_I8 => Value::I64(slot.llVal),
#[allow(clippy::cast_possible_wrap)]
VT_UI8 => Value::I64(slot.ullVal as i64),
VT_R8 => Value::F64(slot.dblVal),
VT_BSTR => Value::Str(slot.bstrVal.to_string()),
VT_DISPATCH => (*slot.pdispVal).as_ref().map_or(Value::Null, |dispatch| {
Value::Object(Box::new(ComDispatch::new(dispatch.clone())))
}),
VT_UNKNOWN => match (*slot.punkVal).as_ref() {
None => Value::Null,
Some(unknown) => Value::Object(Box::new(ComDispatch::new(
unknown
.cast::<IDispatch>()
.map_err(|_| ComError::UnexpectedType {
expected: "an object supporting IDispatch",
actual: "an IUnknown-only object",
})?,
))),
},
_ => {
return Err(ComError::UnexpectedType {
expected: "a VARIANT type this layer models",
actual: "unsupported VARENUM",
});
}
};
Ok(value)
}
}
pub(crate) const fn as_mut_ptr(&mut self) -> *mut VARIANT {
&raw mut self.0
}
}
impl Drop for OwnedVariant {
fn drop(&mut self) {
let _ = unsafe { VariantClear(&raw mut self.0) };
}
}
#[cfg(test)]
mod tests {
use super::OwnedVariant;
use crate::error::ComError;
use crate::value::Value;
fn round_trip(value: &Value) -> Result<Value, ComError> {
OwnedVariant::from_value(value)?.to_value()
}
#[test]
fn round_trips_i32() -> Result<(), ComError> {
assert_eq!(round_trip(&Value::I32(-42))?.as_i32()?, -42);
Ok(())
}
#[test]
fn round_trips_i64_beyond_i32_range() -> Result<(), ComError> {
let big = i64::from(i32::MAX) + 1;
assert_eq!(round_trip(&Value::I64(big))?.as_i64()?, big);
Ok(())
}
#[test]
fn round_trips_f64() -> Result<(), ComError> {
let actual = round_trip(&Value::F64(1.5))?.as_f64()?;
assert!((actual - 1.5).abs() < f64::EPSILON, "got {actual}");
Ok(())
}
#[test]
fn round_trips_bool_both_ways() -> Result<(), ComError> {
assert!(round_trip(&Value::Bool(true))?.as_bool()?);
assert!(!round_trip(&Value::Bool(false))?.as_bool()?);
Ok(())
}
#[test]
fn round_trips_string_including_non_ascii() -> Result<(), ComError> {
let text = "sequence — ünïcode";
assert_eq!(
round_trip(&Value::Str(text.to_owned()))?.into_string()?,
text
);
Ok(())
}
#[test]
fn round_trips_empty_and_null() -> Result<(), ComError> {
let empty = round_trip(&Value::Empty)?;
assert!(matches!(empty, Value::Empty), "got {empty:?}");
let null = round_trip(&Value::Null)?;
assert!(matches!(null, Value::Null), "got {null:?}");
Ok(())
}
#[test]
fn a_string_variant_frees_itself_on_drop() -> Result<(), ComError> {
for _ in 0..1000 {
drop(OwnedVariant::from_value(&Value::Str(
"leak check".to_owned(),
))?);
}
Ok(())
}
}