win-idispatch 0.3.0

This is a rust crate that aims to provide a more ergonomic way of working with idispatch in winapi based projects.
Documentation
use std::{convert::TryFrom, ptr};

use log::trace;
use widestring::WideCString;
use winapi::{shared::{guiddef::IID_NULL, minwindef::{UINT, WORD}, winerror::NOERROR, wtypes::{VT_ARRAY, VT_SAFEARRAY}}, um::{
        oaidl::{DISPID, DISPID_PROPERTYPUT, DISPPARAMS, EXCEPINFO, LPDISPATCH, VARIANT},
        oleauto::{
            VariantClear, VariantInit, DISPATCH_METHOD, DISPATCH_PROPERTYGET, DISPATCH_PROPERTYPUT,
        },
        winnls::GetUserDefaultLCID,
    }};

use win_variant::{free_variant, free_variants, VariantArg, VariantResult};

use super::Error;

#[derive(PartialEq)]
enum DispatchOperation {
    Method,
    GetProperty,
    PutProperty,
}

impl From<DispatchOperation> for WORD {
    fn from(value: DispatchOperation) -> WORD {
        match value {
            DispatchOperation::Method => DISPATCH_METHOD,
            DispatchOperation::GetProperty => DISPATCH_PROPERTYGET,
            DispatchOperation::PutProperty => DISPATCH_PROPERTYPUT,
        }
    }
}

/// Holds the pointer to the IDispatch interface. Drop is implemented to
/// correctly release the pointer when dropped.
pub struct DispatchInterface {
    dispatch: LPDISPATCH,
    released: bool,
}

impl DispatchInterface {
    /// Creates a new Dispatch Interface using the provided pointer. The pointer
    /// should not be managed by anything else after provided to the interface.
    pub fn new(dispatch: LPDISPATCH) -> DispatchInterface {
        DispatchInterface {
            dispatch,
            released: false,
        }
    }

    fn get_ids_of_name(&self, name: &str) -> Result<DISPID, Error> {
        let mut wide: Vec<u16> = WideCString::from_str(name)?.into_vec();
        let mut names = vec![wide.as_mut_ptr()];
        let mut id: DISPID = 0;
        let result = unsafe {
            if let Some(r) = self.dispatch.as_ref() {
                r.GetIDsOfNames(
                    &IID_NULL,
                    names.as_mut_ptr(),
                    1,
                    GetUserDefaultLCID(),
                    &mut id,
                )
            } else {
                return Err(Error::NullDispatchPointer);
            }
        };
        if result != NOERROR {
            return Err(result.into());
        }
        Ok(id)
    }

    fn invoke(
        &self,
        operation: DispatchOperation,
        name: &str,
        args: Option<Vec<VariantArg>>,
    ) -> Result<VARIANT, Error> {
        let command_id = self.get_ids_of_name(name)?;
        let mut args: Option<Vec<VARIANT>> = match args {
            Some(mut args) => {
                // Arguments must be reversed
                args.reverse();
                Some(args.into_iter().map(|a| a.into()).collect())
            }
            None => None,
        };
        let mut params: DISPPARAMS = DISPPARAMS::default();
        if let Some(args) = args.as_mut() {
            params.cArgs = args.len() as UINT;
            params.rgvarg = &mut args[0];
        }
        let mut named_params = if operation == DispatchOperation::PutProperty {
            Some(vec![DISPID_PROPERTYPUT])
        } else {
            None
        };
        if let Some(named_params) = named_params.as_mut() {
            params.rgdispidNamedArgs = &mut named_params[0];
            params.cNamedArgs = named_params.len() as UINT;
        }

        let mut result: VARIANT = VARIANT::default();
        unsafe { VariantInit(&mut result) };

        let mut expect = EXCEPINFO::default();

        let mut arg_error: UINT = 0;
        let hr = unsafe {
            if let Some(r) = self.dispatch.as_ref() {
                r.Invoke(
                    command_id,
                    &IID_NULL,
                    GetUserDefaultLCID(),
                    operation.into(),
                    &mut params,
                    &mut result,
                    &mut expect,
                    &mut arg_error,
                )
            } else {
                VariantClear(&mut result);
                if let Some(args) = args.as_mut() {
                    free_variants(args);
                }
                return Err(Error::NullDispatchPointer);
            }
        };
        if let Some(args) = args.as_mut() {
            free_variants(args);
        }
        if hr != NOERROR {
            return Err(hr.into());
        }
        Ok(result)
    }
    /// This executes a dispatch method operation for the name provided and
    /// passes the arguments to the operation.
    pub fn call(&self, name: &str, args: Option<Vec<VariantArg>>) -> Result<VariantResult, Error> {
        match self.invoke(DispatchOperation::Method, name, args) {
            Err(e) => Err(e),
            Ok(mut v) => {
                let result = VariantResult::try_from(v);
                unsafe {
                    let vt = v.n1.n2().vt as u32;
                    if vt != VT_SAFEARRAY && vt&VT_ARRAY == 0 {
                        free_variant(&mut v);
                    }
                }
                Ok(result?)
            }
        }
    }

    /// This executes a property get operation for the name provided and
    /// passes the arguments to the operation.
    pub fn get(&self, name: &str, args: Option<Vec<VariantArg>>) -> Result<VariantResult, Error> {
        match self.invoke(DispatchOperation::GetProperty, name, args) {
            Err(e) => Err(e),
            Ok(mut v) => {
                let result = VariantResult::try_from(v);
                unsafe {
                    let vt = v.n1.n2().vt as u32;
                    if vt != VT_SAFEARRAY && vt&VT_ARRAY == 0 {
                        free_variant(&mut v);
                    }
                }
                Ok(result?)
            }
        }
    }
    /// This executes a property put operation for the name provided and
    /// passes the arguments to the operation.
    pub fn put(&self, name: &str, args: Option<Vec<VariantArg>>) -> Result<VariantResult, Error> {
        match self.invoke(DispatchOperation::PutProperty, name, args) {
            Err(e) => Err(e),
            Ok(mut v) => {
                let result = VariantResult::try_from(v);
                unsafe {
                    let vt = v.n1.n2().vt as u32;
                    if vt != VT_SAFEARRAY && vt&VT_ARRAY == 0 {
                        free_variant(&mut v);
                    }
                }
                Ok(result?)
            }
        }
    }

    fn release(&mut self) {
        if self.released {
            return;
        }
        unsafe {
            if let Some(r) = self.dispatch.as_ref() {
                r.Release();
            }
            self.released = true;
            self.dispatch = ptr::null_mut();
        }
    }
}

impl Drop for DispatchInterface {
    fn drop(&mut self) {
        trace!("dropping Dispatch interface");
        self.release();
    }
}