use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use windows::core::{BSTR, GUID, PCWSTR};
use windows::Win32::System::Com::*;
use windows::Win32::System::Variant::VARIANT;
use crate::com::variant::Variant;
use crate::error::{OaError, OaResult};
const IID_NULL: GUID = GUID::zeroed();
const DISPID_PROPERTYPUT: i32 = -3;
const LCID_EN_US: u32 = 0x0409;
pub struct Dispatch {
inner: IDispatch,
dispid_cache: Rc<RefCell<HashMap<String, i32>>>,
}
impl Dispatch {
pub fn new(inner: IDispatch) -> Self {
Self {
inner,
dispid_cache: Rc::new(RefCell::new(HashMap::new())),
}
}
pub fn new_with_cache(inner: IDispatch, cache: Rc<RefCell<HashMap<String, i32>>>) -> Self {
Self {
inner,
dispid_cache: cache,
}
}
pub fn cache(&self) -> Rc<RefCell<HashMap<String, i32>>> {
self.dispid_cache.clone()
}
#[allow(dead_code)]
pub fn as_raw(&self) -> &IDispatch {
&self.inner
}
#[allow(dead_code)]
pub fn into_raw(self) -> IDispatch {
self.inner
}
fn get_dispid(&mut self, name: &str) -> OaResult<i32> {
if let Some(&id) = self.dispid_cache.borrow().get(name) {
return Ok(id);
}
let wide_name = BSTR::from(name);
let names = [PCWSTR(wide_name.as_ptr())];
let mut dispid: i32 = 0;
unsafe {
self.inner.GetIDsOfNames(
&IID_NULL,
names.as_ptr(),
1,
LCID_EN_US,
&mut dispid,
)?;
}
self.dispid_cache.borrow_mut().insert(name.to_string(), dispid);
Ok(dispid)
}
pub fn get(&mut self, name: &str) -> OaResult<Variant> {
let dispid = self.get_dispid(name)?;
self.invoke_raw(dispid, DISPATCH_PROPERTYGET, &[])
}
#[allow(dead_code)]
pub fn get_with(&mut self, name: &str, args: &[Variant]) -> OaResult<Variant> {
let dispid = self.get_dispid(name)?;
self.invoke_raw(dispid, DISPATCH_PROPERTYGET, args)
}
pub fn put(&mut self, name: &str, value: impl Into<Variant>) -> OaResult<()> {
let dispid = self.get_dispid(name)?;
let value = value.into();
let mut args = [value.into_inner()];
let mut named_args = [DISPID_PROPERTYPUT];
let params = DISPPARAMS {
rgvarg: args.as_mut_ptr(),
rgdispidNamedArgs: named_args.as_mut_ptr(),
cArgs: 1,
cNamedArgs: 1,
};
let mut result = VARIANT::default();
let mut excep = EXCEPINFO::default();
unsafe {
self.inner.Invoke(
dispid,
&IID_NULL,
LCID_EN_US,
DISPATCH_PROPERTYPUT,
¶ms,
Some(&mut result),
Some(&mut excep),
None,
)?;
}
Ok(())
}
pub fn call(&mut self, name: &str, args: &[Variant]) -> OaResult<Variant> {
let dispid = self.get_dispid(name)?;
self.invoke_raw(
dispid,
DISPATCH_FLAGS(DISPATCH_METHOD.0 | DISPATCH_PROPERTYGET.0),
args,
)
}
pub fn call0(&mut self, name: &str) -> OaResult<Variant> {
self.call(name, &[])
}
pub fn nav(&mut self, path: &str) -> OaResult<Dispatch> {
let segments: Vec<&str> = path.split('.').collect();
if segments.is_empty() {
return Err(OaError::Other("Empty navigation path".into()));
}
let mut current = self.get(segments[0])?.as_dispatch()?;
let mut current_dispatch = Dispatch::new(current);
for &segment in &segments[1..] {
current = current_dispatch.get(segment)?.as_dispatch()?;
current_dispatch = Dispatch::new(current);
}
Ok(current_dispatch)
}
fn invoke_raw(
&self,
dispid: i32,
flags: DISPATCH_FLAGS,
args: &[Variant],
) -> OaResult<Variant> {
let mut raw_args: Vec<VARIANT> = args.iter().rev().map(|a| a.0.clone()).collect();
let params = DISPPARAMS {
rgvarg: if raw_args.is_empty() {
std::ptr::null_mut()
} else {
raw_args.as_mut_ptr()
},
rgdispidNamedArgs: std::ptr::null_mut(),
cArgs: raw_args.len() as u32,
cNamedArgs: 0,
};
let mut result = VARIANT::default();
let mut excep = EXCEPINFO::default();
let mut arg_err: u32 = 0;
unsafe {
self.inner
.Invoke(
dispid,
&IID_NULL,
LCID_EN_US,
flags,
¶ms,
Some(&mut result),
Some(&mut excep),
Some(&mut arg_err),
)
.map_err(|e| {
if !excep.bstrDescription.is_empty() {
OaError::Com(windows::core::Error::new(
e.code(),
excep.bstrDescription.to_string(),
))
} else {
OaError::Com(e)
}
})?;
}
Ok(Variant::from(result))
}
}
impl std::fmt::Debug for Dispatch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Dispatch").finish()
}
}
impl Clone for Dispatch {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
dispid_cache: self.dispid_cache.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dispid_propertyput_constant() {
assert_eq!(DISPID_PROPERTYPUT, -3);
}
#[test]
fn test_dispatch_flags_combine() {
let combined = DISPATCH_FLAGS(DISPATCH_METHOD.0 | DISPATCH_PROPERTYGET.0);
assert_eq!(combined.0, 0x1 | 0x2);
assert_eq!(combined.0, 3);
}
#[test]
fn test_lcid_en_us() {
assert_eq!(LCID_EN_US, 0x0409);
}
}