Skip to content

Instantly share code, notes, and snippets.

@iancormac84
Created November 25, 2018 13:57
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save iancormac84/a1799117ece87c608b94f818f388f1c5 to your computer and use it in GitHub Desktop.
Save iancormac84/a1799117ece87c608b94f818f388f1c5 to your computer and use it in GitHub Desktop.
Gist showing attempt to use Ole Automation to invoke OLE objects
[package]
name = "accessole"
version = "0.1.0"
authors = ["iancormac84 <wilnathan@gmail.com>"]
edition = "2018"
[dependencies]
failure = "*"
oaidl = { git = "https://github.com/ZerothLaw/oaidl-safe", branch = "develop" }
winapi = "0.3.6"
use failure::{Context, Error};
use oaidl::{Ptr, Variant, VariantExt, VariantWrapper, VtEmpty};
use std::{ffi::OsStr, fmt, io, mem, os::windows::ffi::OsStrExt, ptr};
use winapi::{
shared::{
guiddef::{CLSID, GUID, IID_NULL},
winerror::SUCCEEDED,
},
um::{
combaseapi::{
CLSIDFromProgID, CLSIDFromString, CoCreateInstance, CoInitializeEx, CoUninitialize,
CLSCTX_SERVER,
},
oaidl::{IDispatch, DISPID_PROPERTYPUT, DISPPARAMS, EXCEPINFO, VARIANT},
objbase::COINIT_MULTITHREADED,
oleauto::{
DISPATCH_METHOD, DISPATCH_PROPERTYGET, DISPATCH_PROPERTYPUT, DISPATCH_PROPERTYPUTREF,
},
unknwnbase::IUnknown,
winnls::GetUserDefaultLCID,
winnt::HRESULT,
},
Interface,
};
thread_local!(static COM_INITIALIZED: ComInitialized = {
unsafe {
// this call can fail if another library initialized COM in single-threaded mode
// handling this situation properly would make the API more annoying, so we just don't care
check_result(CoInitializeEx(ptr::null_mut(), COINIT_MULTITHREADED)).unwrap();
ComInitialized(ptr::null_mut())
}
});
#[inline]
fn check_result(result: HRESULT) -> OleResult<()> {
if result < 0 {
Err(io::Error::from_raw_os_error(result).into())
} else {
Ok(())
}
}
/// RAII object that guards the fact that COM is initialized.
///
// We store a raw pointer because it's the only way at the moment to remove `Send`/`Sync` from the
// object.
struct ComInitialized(*mut ());
impl Drop for ComInitialized {
#[inline]
fn drop(&mut self) {
unsafe { CoUninitialize() };
}
}
/// Ensures that COM is initialized in this thread.
#[inline]
pub fn com_initialized() {
COM_INITIALIZED.with(|_| {});
}
pub use failure::Error as OleError;
pub type OleResult<T> = std::result::Result<T, OleError>;
pub trait OleResultExt<T, E> {
fn chain_err<F, D>(self, f: F) -> Result<T, Context<D>>
where
F: FnOnce() -> D,
D: fmt::Display + Send + Sync + 'static;
}
impl<T, E> OleResultExt<T, E> for Result<T, E>
where
E: Into<Error>,
{
fn chain_err<F, D>(self, f: F) -> Result<T, Context<D>>
where
F: FnOnce() -> D,
D: fmt::Display + Send + Sync + 'static,
{
self.map_err(|failure| {
let context = f();
failure.into().context(context)
})
}
}
pub fn to_u16s<S: AsRef<OsStr>>(s: S) -> OleResult<Vec<u16>> {
fn inner(s: &OsStr) -> OleResult<Vec<u16>> {
let mut maybe_result: Vec<u16> = s.encode_wide().collect();
if maybe_result.iter().any(|&u| u == 0) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"strings passed to WinAPI cannot contain NULs",
)
.into());
}
maybe_result.push(0);
Ok(maybe_result)
}
inner(s.as_ref())
}
pub fn class_id_from<S: AsRef<OsStr>>(s: S) -> OleResult<GUID> {
let mut hr;
let prog_id = to_u16s(s)?;
let mut clsid: CLSID = unsafe { mem::zeroed() };
unsafe {
hr = CLSIDFromProgID(prog_id.as_ptr(), &mut clsid);
if !SUCCEEDED(hr) {
hr = CLSIDFromString(prog_id.as_ptr(), &mut clsid);
if !SUCCEEDED(hr) {
Err(io::Error::from_raw_os_error(hr).into())
} else {
Ok(clsid)
}
} else {
Ok(clsid)
}
}
}
pub fn create_instance<I: Interface>(clsid: &GUID) -> OleResult<*mut I> {
let mut ppv: *mut I = ptr::null_mut();
let hr = unsafe {
CoCreateInstance(
clsid,
ptr::null_mut(),
CLSCTX_SERVER,
&I::uuidof(),
&mut ppv as *mut *mut I as *mut _,
)
};
if !SUCCEEDED(hr) {
Err(io::Error::from_raw_os_error(hr).into())
} else {
Ok(ppv as *mut I)
}
}
pub fn create_object<S: AsRef<OsStr>>(s: S) -> OleResult<*mut IUnknown> {
let class_id = class_id_from(s)?;
create_instance(&class_id)
}
pub struct OleObject {
dispatch: *mut IDispatch,
}
impl OleObject {
pub fn new<S: AsRef<OsStr>>(ole_object: S) -> OleResult<OleObject> {
com_initialized();
let res = create_object(ole_object)?;
unsafe {
let mut ppv: *mut IDispatch = ptr::null_mut();
let hr = (*res).QueryInterface(
&IDispatch::uuidof(),
&mut ppv as *mut *mut IDispatch as *mut _,
);
if !SUCCEEDED(hr) {
Err(io::Error::from_raw_os_error(hr).into())
} else {
Ok(OleObject {
dispatch: ppv as *mut IDispatch,
})
}
}
}
pub fn call_method<S: AsRef<OsStr> + Copy>(
&mut self,
name: S,
params: Option<&dyn VariantWrapper>,
) -> OleResult<Ptr<VARIANT>> {
let res = self.get_single_id_of_name(name)?;
let params = if let Some(params) = params {
vec![params]
} else {
vec![]
};
self.invoke(res, DISPATCH_METHOD, &params[..])
}
//Below method ideally needs trait constraints that allow user to input raw Rust types and have them be converted to Variant types seamlessly
pub fn put_property<S, D, T>(&mut self, name: S, params: D) -> OleResult<Ptr<VARIANT>>
where
S: AsRef<OsStr> + Copy,
D: VariantExt<T>,
{
let res = self.get_single_id_of_name(name)?;
let val = Variant::wrap(params);
let val = VariantExt::<_>::into_variant(val).unwrap();
let params = vec![val];
self.invoke(res, DISPATCH_PROPERTYPUT, &params[..])
}
pub fn put_property_ref<S, D, T>(&mut self, name: S, params: D) -> OleResult<Ptr<VARIANT>>
where
S: AsRef<OsStr> + Copy,
D: VariantExt<T>,
{
let res = self.get_single_id_of_name(name)?;
let val = Variant::wrap(params);
let val = VariantExt::<_>::into_variant(val).unwrap();
let params = vec![val];
self.invoke(res, DISPATCH_PROPERTYPUTREF, &params[..])
}
pub fn get_property<S: AsRef<OsStr> + Copy>(&mut self, name: S) -> OleResult<Ptr<VARIANT>> {
let res = self.get_single_id_of_name(name)?;
let params = Vec::new();
self.invoke(res, DISPATCH_PROPERTYGET, &params[..])
}
pub fn get_ids_of_names<S: AsRef<OsStr> + Copy>(&mut self, names: &[S]) -> OleResult<Vec<i32>> {
let namelen = names.len();
let mut wnames = vec![vec![]; namelen];
for i in 0..namelen {
wnames[i] = to_u16s(names[i])?;
}
let mut dispid = vec![0i32; names.len()];
let hr = unsafe {
(*self.dispatch).GetIDsOfNames(
&IID_NULL,
&mut wnames[0].as_mut_ptr(),
namelen as u32,
GetUserDefaultLCID(),
dispid.as_mut_ptr(),
)
};
if !SUCCEEDED(hr) {
Err(io::Error::from_raw_os_error(hr).into())
} else {
Ok(dispid)
}
}
pub fn get_single_id_of_name<S: AsRef<OsStr> + Copy>(&mut self, name: S) -> OleResult<i32> {
let ids = self.get_ids_of_names(&[name])?;
Ok(ids[0])
}
fn invoke<D, T>(
&mut self,
dispid: i32,
dispatch: u16,
params: &[Ptr<VARIANT>],
) -> OleResult<Ptr<VARIANT>> {
let mut dispparams = DISPPARAMS {
rgvarg: ptr::null_mut(),
rgdispidNamedArgs: ptr::null_mut(),
cArgs: 0,
cNamedArgs: 0,
};
let mut dispnames: [i32; 1] = [0; 1];
if dispatch & DISPATCH_PROPERTYPUT != 0 || dispatch & DISPATCH_PROPERTYPUTREF != 0 {
dispnames[0] = DISPID_PROPERTYPUT;
dispparams.rgdispidNamedArgs = &mut dispnames[0];
dispparams.cNamedArgs = 1;
}
let params_len = params.len();
if params_len > 0 {
dispparams.rgvarg = params[0].as_ptr();
dispparams.cArgs = params_len as u32;
}
let mut result = VariantExt::<_>::into_variant(VtEmpty).unwrap();
let mut excep_info: EXCEPINFO = unsafe { mem::zeroed() };
let hr = unsafe {
(*self.dispatch).Invoke(
dispid,
&IID_NULL,
GetUserDefaultLCID(),
dispatch,
&mut dispparams,
result.as_ptr(),
&mut excep_info,
ptr::null_mut(),
)
};
if !SUCCEEDED(hr) {
Err(io::Error::from_raw_os_error(hr).into())
} else {
Ok(result)
}
}
}
pub trait OleAutomation {
const DISPATCH_ID: &'static str;
}
fn main() {
let mut ole_obj = OleObject::new("Excel.Application").unwrap();
let res = ole_obj.put_property("Visible", true); //Not possible because the trait `oaidl::variant::private::VariantAccess` is not implemented for `bool`
//println!("res is {:?}", res);
let res = ole_obj.get_property("Workbooks");
//println!("res is {:?}", res);
/*let res = res.unwrap();
if u32::from(res.vartype()) == VT_DISPATCH {
let mut dispatch = res.into_c_variant();
let dispatch = unsafe {
dispatch.n1.n2_mut().n3.pdispVal()
};
}*/
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment