use windows::Win32::Foundation::VARIANT_BOOL;
use windows::Win32::System::Com::{IDispatch, SAFEARRAY};
use windows::Win32::System::Ole::{SafeArrayGetElement, SafeArrayGetLBound, SafeArrayGetUBound};
use windows::Win32::System::Variant::{
VARIANT, VT_ARRAY, VT_BOOL, VT_BSTR, VT_BYREF, 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())
}
#[must_use]
pub(crate) unsafe fn byref_text(cell: *mut BSTR) -> Self {
let mut owned = Self::empty();
unsafe {
let inner = &mut owned.0.Anonymous.Anonymous;
inner.vt = windows::Win32::System::Variant::VARENUM(VT_BYREF.0 | VT_BSTR.0);
inner.Anonymous.pbstrVal = cell;
}
owned
}
#[must_use]
pub(crate) unsafe fn byref_i32(cell: *mut i32) -> Self {
let mut owned = Self::empty();
unsafe {
let inner = &mut owned.0.Anonymous.Anonymous;
inner.vt = windows::Win32::System::Variant::VARENUM(VT_BYREF.0 | VT_I4.0);
inner.Anonymous.plVal = cell;
}
owned
}
#[must_use]
pub(crate) unsafe fn byref_bool(cell: *mut VARIANT_BOOL) -> Self {
let mut owned = Self::empty();
unsafe {
let inner = &mut owned.0.Anonymous.Anonymous;
inner.vt = windows::Win32::System::Variant::VARENUM(VT_BYREF.0 | VT_BOOL.0);
inner.Anonymous.pboolVal = cell;
}
owned
}
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::I32Array(_) => {
return Err(ComError::UnexpectedType {
expected: "a VARIANT type this layer can send",
actual: "an array, which is read-only here",
});
}
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",
})?,
))),
},
variant_type if variant_type.0 == VT_ARRAY.0 | VT_I4.0 => {
Value::I32Array(read_i32_array(slot.parray)?)
}
_ => {
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
}
}
fn read_i32_array(array: *mut SAFEARRAY) -> Result<Vec<i32>, ComError> {
if array.is_null() {
return Err(ComError::UnexpectedType {
expected: "a SAFEARRAY of i32",
actual: "a null array pointer",
});
}
unsafe {
let lower = SafeArrayGetLBound(array, 1)
.map_err(|error| ComError::hresult(error.code().0, "SafeArray"))?;
let upper = SafeArrayGetUBound(array, 1)
.map_err(|error| ComError::hresult(error.code().0, "SafeArray"))?;
if upper < lower {
return Ok(Vec::new());
}
let mut elements = Vec::with_capacity((upper - lower + 1).unsigned_abs() as usize);
for index in lower..=upper {
let mut element: i32 = 0;
SafeArrayGetElement(array, &raw const index, (&raw mut element).cast())
.map_err(|error| ComError::hresult(error.code().0, "SafeArray"))?;
elements.push(element);
}
Ok(elements)
}
}
impl Drop for OwnedVariant {
fn drop(&mut self) {
let variant_type = unsafe { self.0.Anonymous.Anonymous.vt };
if variant_type.0 & VT_BYREF.0 != 0 {
return;
}
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 a_by_reference_string_slot_carries_what_the_callee_writes() {
use windows::Win32::System::Variant::{VT_BSTR, VT_BYREF};
use windows_core::BSTR;
let mut cell = Box::new(BSTR::new());
let slot = unsafe { OwnedVariant::byref_text(&raw mut *cell) };
unsafe {
let inner = &slot.0.Anonymous.Anonymous;
assert_eq!(
inner.vt.0,
VT_BYREF.0 | VT_BSTR.0,
"the slot should be a by-reference BSTR",
);
let written = inner.Anonymous.pbstrVal;
*written = BSTR::from("expression is not valid");
}
assert_eq!(cell.to_string(), "expression is not valid");
}
#[test]
fn a_by_reference_integer_slot_carries_what_the_callee_writes() {
use windows::Win32::System::Variant::{VT_BYREF, VT_I4};
let mut cell = Box::new(0_i32);
let slot = unsafe { OwnedVariant::byref_i32(&raw mut *cell) };
unsafe {
let inner = &slot.0.Anonymous.Anonymous;
assert_eq!(
inner.vt.0,
VT_BYREF.0 | VT_I4.0,
"the slot should be a by-reference i32",
);
let written = inner.Anonymous.plVal;
*written = 12;
}
assert_eq!(*cell, 12);
}
fn i32_array_variant(elements: &[i32]) -> Result<OwnedVariant, ComError> {
use windows::Win32::System::Ole::{SafeArrayCreateVector, SafeArrayPutElement};
use windows::Win32::System::Variant::{VARENUM, VT_ARRAY, VT_I4};
unsafe {
let count = u32::try_from(elements.len()).unwrap_or(0);
let array = SafeArrayCreateVector(VT_I4, 0, count);
assert!(!array.is_null(), "the OLE allocator refused a small array");
for (index, element) in elements.iter().enumerate() {
let position = i32::try_from(index).unwrap_or(0);
SafeArrayPutElement(array, &raw const position, (&raw const *element).cast())
.map_err(|error| ComError::hresult(error.code().0, "SafeArrayPutElement"))?;
}
let mut owned = OwnedVariant::empty();
let inner = &mut owned.0.Anonymous.Anonymous;
inner.vt = VARENUM(VT_ARRAY.0 | VT_I4.0);
inner.Anonymous.parray = array;
Ok(owned)
}
}
#[test]
fn an_i32_array_variant_reads_back_as_its_elements() -> Result<(), ComError> {
let elements = i32_array_variant(&[7, 11, 13])?
.to_value()?
.into_i32_array()?;
assert_eq!(elements, vec![7, 11, 13]);
Ok(())
}
#[test]
fn an_empty_i32_array_is_empty_rather_than_an_error() -> Result<(), ComError> {
let elements = i32_array_variant(&[])?.to_value()?.into_i32_array()?;
assert!(elements.is_empty());
Ok(())
}
#[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(())
}
}