Created
November 25, 2018 13:57
-
-
Save iancormac84/a1799117ece87c608b94f818f388f1c5 to your computer and use it in GitHub Desktop.
Gist showing attempt to use Ole Automation to invoke OLE objects
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, ¶ms[..]) | |
} | |
//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, ¶ms[..]) | |
} | |
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, ¶ms[..]) | |
} | |
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, ¶ms[..]) | |
} | |
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