diff --git a/crates/oadef/Cargo.toml b/crates/oadef/Cargo.toml index 32f3562..0f6911d 100644 --- a/crates/oadef/Cargo.toml +++ b/crates/oadef/Cargo.toml @@ -5,3 +5,15 @@ edition = "2021" license = "MIT OR Apache-2.0" publish = false rust-version = "1.73.0" + +[dependencies] +thiserror = "1.0.49" + +[dependencies.windows] +version = "0.51.1" +features = [ + "Win32_Foundation", + "Win32_System_Com", + "Win32_System_Ole", + "Win32_System_Variant", +] diff --git a/crates/oadef/src/lib.rs b/crates/oadef/src/lib.rs index 8b13789..dc8fcc7 100644 --- a/crates/oadef/src/lib.rs +++ b/crates/oadef/src/lib.rs @@ -1 +1,516 @@ +use std::{ffi::c_void, mem::ManuallyDrop, slice}; +use thiserror::Error; +use windows::{ + core::{BSTR, GUID, PCWSTR}, + Win32::{ + Foundation::VARIANT_BOOL, + System::{ + Com::{ + CLSIDFromProgID, CoCreateInstance, IDispatch, CLSCTX_ALL, DISPATCH_METHOD, + DISPATCH_PROPERTYGET, DISPATCH_PROPERTYPUT, DISPPARAMS, SAFEARRAY, + }, + Ole::{SafeArrayCreateVector, SafeArrayPutElement}, + Variant::{ + VARENUM, VARIANT, VARIANT_0_0_0, VT_ARRAY, VT_BOOL, VT_BSTR, VT_DISPATCH, VT_EMPTY, + VT_I2, VT_I4, VT_I8, VT_R4, VT_R8, VT_SAFEARRAY, VT_UI2, VT_UI4, VT_UI8, + }, + }, + }, +}; + +pub unsafe fn idispatch_from_prog_id(lpszprogid: PCWSTR) -> windows::core::Result { + let clsid = CLSIDFromProgID(lpszprogid)?; + CoCreateInstance(&clsid, None, CLSCTX_ALL) +} + +pub unsafe fn invoke>( + disp: &IDispatch, + name: PCWSTR, + op: Op, +) -> crate::Result { + let mut rgdispid = 0; + disp.GetIDsOfNames( + &GUID::zeroed(), + &name, + 1, + LOCALE_USER_DEFAULT, + &mut rgdispid, + )?; + + let wflags = match op { + Op::PropertyGet => DISPATCH_PROPERTYGET, + Op::PropertyPut(_) => DISPATCH_PROPERTYPUT, + Op::Method(_) => DISPATCH_METHOD, + }; + + let mut dispparams = match op { + Op::PropertyGet => vec![], + Op::PropertyPut(param) => vec![param.try_into()?], + Op::Method(params) => params + .into_iter() + .rev() + .map(TryInto::try_into) + .collect::, _>>()?, + }; + let dispparams = &DISPPARAMS { + cArgs: dispparams.len() as _, + rgvarg: dispparams.as_mut_ptr(), + ..Default::default() + }; + + let mut result = Default::default(); + let mut excepinfo = Default::default(); + let mut argerr = 0; + + let ok_or_err = disp.Invoke( + rgdispid, + &GUID::zeroed(), + LOCALE_USER_DEFAULT, + wflags, + dispparams, + Some(&mut result), + Some(&mut excepinfo), + Some(&mut argerr), + ); + + if ok_or_err.is_err() { + // FIXME + dbg!(excepinfo); + dbg!(argerr); + } + + ok_or_err?; + + return Out::from_win32(result)?.try_into(); + + const LOCALE_USER_DEFAULT: u32 = 0x400; +} + +pub enum Op { + PropertyGet, + PropertyPut(Arg), + Method(Vec), +} + +pub enum Arg { + Bool(AtMost1D), + Ui2(AtMost1D), + Ui4(AtMost1D), + Ui8(AtMost1D), + I2(AtMost1D), + I4(AtMost1D), + I8(AtMost1D), + R4(AtMost1D), + R8(AtMost1D), + Bstr(AtMost1D), + IDispatch(AtMost1D), +} + +impl TryFrom for VARIANT { + type Error = windows::core::Error; + + fn try_from(arg: Arg) -> windows::core::Result { + #![allow(clippy::explicit_auto_deref)] + + return match arg { + Arg::Bool(AtMost1D::D0(p)) => Ok(unsafe { d0(p) }), + Arg::Bool(AtMost1D::D1(ps)) => unsafe { d1(&ps) }, + Arg::Ui2(AtMost1D::D0(n)) => Ok(unsafe { d0(n) }), + Arg::Ui2(AtMost1D::D1(ns)) => unsafe { d1(&ns) }, + Arg::Ui4(AtMost1D::D0(n)) => Ok(unsafe { d0(n) }), + Arg::Ui4(AtMost1D::D1(ns)) => unsafe { d1(&ns) }, + Arg::Ui8(AtMost1D::D0(n)) => Ok(unsafe { d0(n) }), + Arg::Ui8(AtMost1D::D1(ns)) => unsafe { d1(&ns) }, + Arg::I2(AtMost1D::D0(i)) => Ok(unsafe { d0(i) }), + Arg::I2(AtMost1D::D1(is)) => unsafe { d1(&is) }, + Arg::I4(AtMost1D::D0(i)) => Ok(unsafe { d0(i) }), + Arg::I4(AtMost1D::D1(is)) => unsafe { d1(&is) }, + Arg::I8(AtMost1D::D0(i)) => Ok(unsafe { d0(i) }), + Arg::I8(AtMost1D::D1(is)) => unsafe { d1(&is) }, + Arg::R4(AtMost1D::D0(v)) => Ok(unsafe { d0(v) }), + Arg::R4(AtMost1D::D1(vs)) => unsafe { d1(&vs) }, + Arg::R8(AtMost1D::D0(v)) => Ok(unsafe { d0(v) }), + Arg::R8(AtMost1D::D1(vs)) => unsafe { d1(&vs) }, + Arg::Bstr(AtMost1D::D0(s)) => Ok(unsafe { d0(s) }), + Arg::Bstr(AtMost1D::D1(ss)) => unsafe { d1(&ss) }, + Arg::IDispatch(AtMost1D::D0(x)) => Ok(unsafe { d0(x) }), + Arg::IDispatch(AtMost1D::D1(xs)) => unsafe { d1(&xs) }, + }; + + unsafe fn d0(x: T) -> VARIANT { + let mut v = VARIANT::default(); + (*v.Anonymous.Anonymous).vt = T::VT; + x.set(&mut (*v.Anonymous.Anonymous).Anonymous); + v + } + + unsafe fn d1(xs: &[T]) -> windows::core::Result { + let array = SafeArrayCreateVector(T::VT, 0, xs.len() as _); + if array.is_null() { + todo!(); + } + for (i, x) in xs.iter().enumerate() { + let indices = [i as _]; + SafeArrayPutElement(array, indices.as_ptr(), x as *const T as *const c_void)?; + } + + let mut v = VARIANT::default(); + (*v.Anonymous.Anonymous).vt = VT_SAFEARRAY; + (*v.Anonymous.Anonymous).Anonymous.parray = array; + Ok(v) + } + + trait Element { + const VT: VARENUM; + fn set(self, v: &mut VARIANT_0_0_0); + } + + impl Element for VARIANT_BOOL { + const VT: VARENUM = VT_BOOL; + + fn set(self, v: &mut VARIANT_0_0_0) { + v.boolVal = self; + } + } + + impl Element for u16 { + const VT: VARENUM = VT_UI2; + + fn set(self, v: &mut VARIANT_0_0_0) { + v.uiVal = self; + } + } + + impl Element for u32 { + const VT: VARENUM = VT_UI4; + + fn set(self, v: &mut VARIANT_0_0_0) { + v.ulVal = self; + } + } + + impl Element for u64 { + const VT: VARENUM = VT_UI8; + + fn set(self, v: &mut VARIANT_0_0_0) { + v.ullVal = self; + } + } + + impl Element for i16 { + const VT: VARENUM = VT_I2; + + fn set(self, v: &mut VARIANT_0_0_0) { + v.iVal = self; + } + } + + impl Element for i32 { + const VT: VARENUM = VT_I4; + + fn set(self, v: &mut VARIANT_0_0_0) { + v.lVal = self; + } + } + + impl Element for i64 { + const VT: VARENUM = VT_I8; + + fn set(self, v: &mut VARIANT_0_0_0) { + v.llVal = self; + } + } + + impl Element for f32 { + const VT: VARENUM = VT_R4; + + fn set(self, v: &mut VARIANT_0_0_0) { + v.fltVal = self; + } + } + + impl Element for f64 { + const VT: VARENUM = VT_R8; + + fn set(self, v: &mut VARIANT_0_0_0) { + v.dblVal = self; + } + } + + impl Element for BSTR { + const VT: VARENUM = VT_BSTR; + + fn set(self, v: &mut VARIANT_0_0_0) { + v.bstrVal = ManuallyDrop::new(self); + } + } + + impl Element for IDispatch { + const VT: VARENUM = VT_DISPATCH; + + fn set(self, v: &mut VARIANT_0_0_0) { + v.pdispVal = ManuallyDrop::new(Some(self)); + } + } + } +} + +macro_rules! impl_arg_from_prim { + ($($variant:path : $ty:ty;)*) => { + $( + impl From<$ty> for Arg { + fn from(p: $ty) -> Self { + $variant(AtMost1D::D0(p.into())) + } + } + + impl From<&'_ [$ty]> for Arg { + fn from(ps: &[$ty]) -> Self { + $variant(AtMost1D::D1(ps.iter().copied().map(Into::into).collect())) + } + } + + impl From> for Arg { + fn from(ps: Vec<$ty>) -> Self { + $variant(AtMost1D::D1(ps.into_iter().map(Into::into).collect())) + } + } + )* + }; +} + +impl_arg_from_prim! { + Self::Bool: bool; + Self::Ui2: u16; + Self::Ui4: u32; + Self::Ui8: u64; + Self::I2: i16; + Self::I4: i32; + Self::I8: i64; + Self::R4: f32; + Self::R8: f64; +} + +impl From<&'_ str> for Arg { + fn from(s: &'_ str) -> Self { + Self::Bstr(AtMost1D::D0(s.into())) + } +} + +impl From<&'_ String> for Arg { + fn from(s: &'_ String) -> Self { + Self::Bstr(AtMost1D::D0(s.into())) + } +} + +impl From<&'_ [&'_ str]> for Arg { + fn from(ss: &'_ [&'_ str]) -> Self { + Self::Bstr(AtMost1D::D1(ss.iter().copied().map(Into::into).collect())) + } +} + +impl From<&'_ [String]> for Arg { + fn from(ss: &'_ [String]) -> Self { + Self::Bstr(AtMost1D::D1(ss.iter().map(Into::into).collect())) + } +} + +impl From for Arg { + fn from(disp: IDispatch) -> Self { + Self::IDispatch(AtMost1D::D0(disp)) + } +} + +impl From> for Arg { + fn from(disps: Vec) -> Self { + Self::IDispatch(AtMost1D::D1(disps)) + } +} + +#[derive(Debug)] +pub enum Out { + Empty, + Bool(AtMost1D), + Ui2(AtMost1D), + Ui4(AtMost1D), + Ui8(AtMost1D), + I2(AtMost1D), + I4(AtMost1D), + I8(AtMost1D), + R4(AtMost1D), + R8(AtMost1D), + Bstr(AtMost1D), + IDispatch(AtMost1D), +} + +impl Out { + /// # Safety + /// + /// - `vt` must be valid. + unsafe fn from_win32(win32: VARIANT) -> crate::Result { + return match win32.Anonymous.Anonymous.vt { + VT_EMPTY => Ok(Self::Empty), + VT_BOOL => Ok(Self::Bool(AtMost1D::D0( + win32.Anonymous.Anonymous.Anonymous.boolVal.into(), + ))), + VT_UI2 => Ok(Self::Ui2(AtMost1D::D0( + win32.Anonymous.Anonymous.Anonymous.uiVal, + ))), + VT_UI4 => Ok(Self::Ui4(AtMost1D::D0( + win32.Anonymous.Anonymous.Anonymous.ulVal, + ))), + VT_UI8 => Ok(Self::Ui8(AtMost1D::D0( + win32.Anonymous.Anonymous.Anonymous.ullVal, + ))), + VT_I2 => Ok(Self::I2(AtMost1D::D0( + win32.Anonymous.Anonymous.Anonymous.iVal, + ))), + VT_I4 => Ok(Self::I4(AtMost1D::D0( + win32.Anonymous.Anonymous.Anonymous.lVal, + ))), + VT_I8 => Ok(Self::I8(AtMost1D::D0( + win32.Anonymous.Anonymous.Anonymous.llVal, + ))), + VT_R4 => Ok(Self::R4(AtMost1D::D0( + win32.Anonymous.Anonymous.Anonymous.fltVal, + ))), + VT_R8 => Ok(Self::R8(AtMost1D::D0( + win32.Anonymous.Anonymous.Anonymous.dblVal, + ))), + VT_BSTR => Ok(Self::Bstr(AtMost1D::D0( + win32.Anonymous.Anonymous.Anonymous.bstrVal.to_string(), + ))), + VT_DISPATCH => Ok({ + let win32 = ManuallyDrop::into_inner(win32.Anonymous.Anonymous); + let win32 = ManuallyDrop::into_inner(win32.Anonymous.pdispVal).unwrap(); + Self::IDispatch(AtMost1D::D0(win32)) + }), + vt if vt.0 & VT_ARRAY.0 != 0 => { + let array = win32.Anonymous.Anonymous.Anonymous.parray.read_unaligned(); + + if array.cDims != 1 { + todo!(); + } + + if vt.0 == VT_ARRAY.0 | VT_UI2.0 { + Ok(Self::Ui2(AtMost1D::D1(read_d1(&array).to_owned()))) + } else if vt.0 == VT_ARRAY.0 | VT_UI4.0 { + Ok(Self::Ui4(AtMost1D::D1(read_d1(&array).to_owned()))) + } else if vt.0 == VT_ARRAY.0 | VT_UI8.0 { + Ok(Self::Ui8(AtMost1D::D1(read_d1(&array).to_owned()))) + } else if vt.0 == VT_ARRAY.0 | VT_I2.0 { + Ok(Self::I2(AtMost1D::D1(read_d1(&array).to_owned()))) + } else if vt.0 == VT_ARRAY.0 | VT_I4.0 { + Ok(Self::I4(AtMost1D::D1(read_d1(&array).to_owned()))) + } else if vt.0 == VT_ARRAY.0 | VT_I8.0 { + Ok(Self::I8(AtMost1D::D1(read_d1(&array).to_owned()))) + } else if vt.0 == VT_ARRAY.0 | VT_R4.0 { + Ok(Self::R4(AtMost1D::D1(read_d1(&array).to_owned()))) + } else if vt.0 == VT_ARRAY.0 | VT_R8.0 { + Ok(Self::R8(AtMost1D::D1(read_d1(&array).to_owned()))) + } else if vt.0 == VT_ARRAY.0 | VT_BSTR.0 { + Ok(Self::Bstr(AtMost1D::D1( + read_d1::(&array) + .iter() + .map(ToString::to_string) + .collect(), + ))) + } else if vt.0 == VT_ARRAY.0 | VT_DISPATCH.0 { + Ok(Self::IDispatch(AtMost1D::D1(read_d1(&array).to_owned()))) + } else { + Err(ErrorRepr::UnsupportedDataType(vt).into()) + } + } + vt => Err(ErrorRepr::UnsupportedDataType(vt).into()), + }; + + unsafe fn read_d1(array: &SAFEARRAY) -> &[T] { + slice::from_raw_parts(array.pvData as *const T, array.rgsabound[0].cElements as _) + } + } +} + +impl TryFrom for () { + type Error = crate::Error; + + fn try_from(out: Out) -> std::result::Result { + match out { + Out::Empty => Ok(()), + out => Err(ErrorRepr::UnexpectedType(out).into()), + } + } +} + +macro_rules! try_from_out { + ($($variant:path : $ty:ty;)*) => { + $( + impl TryFrom for $ty { + type Error = crate::Error; + + fn try_from(out: Out) -> std::result::Result { + match out { + $variant(AtMost1D::D0(x)) => Ok(x), + out => Err(ErrorRepr::UnexpectedType(out).into()), + } + } + } + + + impl TryFrom for Vec<$ty> { + type Error = crate::Error; + + fn try_from(out: Out) -> std::result::Result { + match out { + $variant(AtMost1D::D1(xs)) => Ok(xs), + out => Err(ErrorRepr::UnexpectedType(out).into()), + } + } + } + )* + }; +} + +try_from_out! { + Out::Bool: bool; + Out::Ui2: u16; + Out::Ui4: u32; + Out::Ui8: u64; + Out::I2: i16; + Out::I4: i32; + Out::I8: i64; + Out::R4: f32; + Out::R8: f64; + Out::Bstr: String; + Out::IDispatch: IDispatch; +} + +#[derive(Debug)] +pub enum AtMost1D { + D0(T), + D1(Vec), +} + +pub type Result = std::result::Result; + +#[derive(Debug, Error)] +#[error(transparent)] +pub struct Error(#[from] ErrorRepr); + +impl From for Error { + fn from(err: windows::core::Error) -> Self { + ErrorRepr::from(err).into() + } +} + +#[derive(Debug, Error)] +enum ErrorRepr { + #[error(transparent)] + Windows(#[from] windows::core::Error), + + #[error("unsupported data type: {_0:?}")] + UnsupportedDataType(VARENUM), + + #[error("unexpected type: {_0:?}")] + UnexpectedType(Out), +}