use std::fmt;
use windows::Win32::Globalization::LOCALE_USER_DEFAULT;
use windows::Win32::System::Com::{
CLSCTX_ALL, CLSIDFromProgID, COINIT_APARTMENTTHREADED, CoCreateInstance, CoInitializeEx,
CoUninitialize, DISPATCH_FLAGS, DISPATCH_METHOD, DISPATCH_PROPERTYGET, DISPATCH_PROPERTYPUT,
DISPPARAMS, EXCEPINFO, IDispatch,
};
use windows::Win32::System::Variant::VARIANT;
use windows_core::{GUID, HSTRING};
use crate::error::ComError;
use crate::value::Value;
use crate::variant::OwnedVariant;
const DISPID_PROPERTYPUT: i32 = -3;
pub trait Dispatch: fmt::Debug {
fn get(&self, dispid: i32) -> Result<Value, ComError>;
fn put(&self, dispid: i32, value: Value) -> Result<(), ComError>;
fn call(&self, dispid: i32, args: &[Value]) -> Result<Value, ComError>;
fn duplicate(&self) -> Option<Box<dyn Dispatch>> {
None
}
fn as_idispatch(&self) -> Option<&IDispatch> {
None
}
}
#[derive(Debug, Clone)]
pub struct ComDispatch(IDispatch);
impl ComDispatch {
#[must_use]
pub const fn new(dispatch: IDispatch) -> Self {
Self(dispatch)
}
fn invoke(
&self,
dispid: i32,
flags: DISPATCH_FLAGS,
params: &DISPPARAMS,
context: &'static str,
) -> Result<OwnedVariant, ComError> {
let mut result = OwnedVariant::empty();
let mut exception = EXCEPINFO::default();
let mut arg_error = 0u32;
let status = unsafe {
self.0.Invoke(
dispid,
&GUID::zeroed(),
LOCALE_USER_DEFAULT,
flags,
&raw const *params,
Some(result.as_mut_ptr()),
Some(&raw mut exception),
Some(&raw mut arg_error),
)
};
if let Err(error) = status {
let code = if exception.scode == 0 {
error.code().0
} else {
exception.scode
};
return Err(ComError::member(code, context, dispid));
}
Ok(result)
}
}
impl Dispatch for ComDispatch {
fn duplicate(&self) -> Option<Box<dyn Dispatch>> {
Some(Box::new(Self(self.0.clone())))
}
fn as_idispatch(&self) -> Option<&IDispatch> {
Some(&self.0)
}
fn get(&self, dispid: i32) -> Result<Value, ComError> {
let no_args = DISPPARAMS::default();
self.invoke(
dispid,
DISPATCH_PROPERTYGET,
&no_args,
"IDispatch::Invoke (get)",
)?
.to_value()
}
fn put(&self, dispid: i32, value: Value) -> Result<(), ComError> {
let mut argument = OwnedVariant::from_value(&value)?;
let mut put_dispid = DISPID_PROPERTYPUT;
let params = DISPPARAMS {
rgvarg: argument.as_mut_ptr(),
rgdispidNamedArgs: &raw mut put_dispid,
cArgs: 1,
cNamedArgs: 1,
};
self.invoke(
dispid,
DISPATCH_PROPERTYPUT,
¶ms,
"IDispatch::Invoke (put)",
)?;
Ok(())
}
fn call(&self, dispid: i32, args: &[Value]) -> Result<Value, ComError> {
let mut arguments = args
.iter()
.rev()
.map(OwnedVariant::from_value)
.collect::<Result<Vec<_>, _>>()?;
let count = u32::try_from(arguments.len())
.map_err(|_| ComError::hresult(-2_147_024_809, "argument count exceeds COM limit"))?;
let params = DISPPARAMS {
rgvarg: arguments.as_mut_ptr().cast::<VARIANT>(),
rgdispidNamedArgs: std::ptr::null_mut(),
cArgs: count,
cNamedArgs: 0,
};
let result = self.invoke(
dispid,
DISPATCH_METHOD | DISPATCH_PROPERTYGET,
¶ms,
"IDispatch::Invoke (call)",
)?;
drop(arguments);
result.to_value()
}
}
pub fn create_dispatch(prog_id: &str) -> Result<ComDispatch, ComError> {
init_apartment()?;
let prog_id = HSTRING::from(prog_id);
let clsid = unsafe { CLSIDFromProgID(&prog_id) }
.map_err(|error| ComError::hresult(error.code().0, "CLSIDFromProgID"))?;
let dispatch: IDispatch = unsafe { CoCreateInstance(&raw const clsid, None, CLSCTX_ALL) }
.map_err(|error| ComError::hresult(error.code().0, "CoCreateInstance"))?;
Ok(ComDispatch::new(dispatch))
}
pub fn init_apartment() -> Result<(), ComError> {
let result = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) };
if result.is_ok() || result.0 == RPC_E_CHANGED_MODE {
Ok(())
} else {
Err(ComError::hresult(result.0, "CoInitializeEx"))
}
}
const RPC_E_CHANGED_MODE: i32 = -2_147_417_850;
pub fn close_apartment(last: Box<dyn Dispatch>) {
drop(last);
unsafe { CoUninitialize() };
}