use std::marker::PhantomData;
use std::mem;
use std::ops::Deref;
use std::ptr::{self, null_mut, NonNull};
use std::rc::Rc;
use winapi::shared::minwindef::LPVOID;
use winapi::shared::{
winerror::HRESULT,
wtypesbase::{CLSCTX, CLSCTX_INPROC_SERVER, CLSCTX_LOCAL_SERVER},
};
use winapi::um::{
combaseapi::{CoCreateInstance, CoInitializeEx, CoTaskMemFree, CoUninitialize},
objbase::{COINIT_APARTMENTTHREADED, COINIT_MULTITHREADED},
unknwnbase::IUnknown,
};
use winapi::{Class, Interface};
use check_succeeded;
use error::{succeeded_or_err, HResult, ResultExt};
#[repr(transparent)]
pub struct ComRef<T>(NonNull<T>)
where
T: Interface;
impl<T> ComRef<T>
where
T: Interface,
{
pub unsafe fn from_raw(ptr: NonNull<T>) -> ComRef<T> {
ComRef(ptr)
}
pub fn up<U>(self) -> ComRef<U>
where
T: Deref<Target = U>,
U: Interface,
{
unsafe { ComRef::from_raw(NonNull::new(self.into_raw().as_ptr() as *mut U).unwrap()) }
}
pub fn into_raw(self) -> NonNull<T> {
let p = self.0;
mem::forget(self);
p
}
fn as_unknown(&self) -> &IUnknown {
unsafe { &*(self.as_raw_ptr() as *mut IUnknown) }
}
pub fn cast<U>(&self) -> Result<ComRef<U>, HResult>
where
U: Interface,
{
let mut obj = null_mut();
let hr =
succeeded_or_err(unsafe { self.as_unknown().QueryInterface(&U::uuidof(), &mut obj) })?;
NonNull::new(obj as *mut U)
.map(|obj| unsafe { ComRef::from_raw(obj) })
.ok_or_else(|| HResult::new(hr).function("IUnknown::QueryInterface"))
}
pub fn as_raw(&self) -> NonNull<T> {
self.0
}
pub fn as_raw_ptr(&self) -> *mut T {
self.0.as_ptr()
}
}
impl<T> Deref for ComRef<T>
where
T: Interface,
{
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.as_raw_ptr() }
}
}
impl<T> Clone for ComRef<T>
where
T: Interface,
{
fn clone(&self) -> Self {
unsafe {
self.as_unknown().AddRef();
ComRef::from_raw(self.as_raw())
}
}
}
impl<T> Drop for ComRef<T>
where
T: Interface,
{
fn drop(&mut self) {
unsafe {
self.as_unknown().Release();
}
}
}
#[derive(Debug, Default)]
pub struct ComApartmentScope {
_do_not_send: PhantomData<Rc<()>>,
}
impl ComApartmentScope {
pub fn init_sta() -> Result<Self, HResult> {
unsafe { check_succeeded!(CoInitializeEx(ptr::null_mut(), COINIT_APARTMENTTHREADED)) }?;
Ok(Default::default())
}
pub fn init_mta() -> Result<Self, HResult> {
unsafe { check_succeeded!(CoInitializeEx(ptr::null_mut(), COINIT_MULTITHREADED)) }?;
Ok(Default::default())
}
}
impl Drop for ComApartmentScope {
fn drop(&mut self) {
unsafe {
CoUninitialize();
}
}
}
thread_local! {
pub static INIT_STA: Result<ComApartmentScope, HResult> = ComApartmentScope::init_sta();
pub static INIT_MTA: Result<ComApartmentScope, HResult> = ComApartmentScope::init_mta();
}
pub fn create_instance<C, I>(ctx: CLSCTX) -> Result<ComRef<I>, HResult>
where
C: Class,
I: Interface,
{
get(|interface| unsafe {
CoCreateInstance(
&C::uuidof(),
ptr::null_mut(), ctx,
&I::uuidof(),
interface as *mut *mut _,
)
})
.function("CoCreateInstance")
}
pub fn create_instance_local_server<C, I>() -> Result<ComRef<I>, HResult>
where
C: Class,
I: Interface,
{
create_instance::<C, I>(CLSCTX_LOCAL_SERVER)
}
pub fn create_instance_inproc_server<C, I>() -> Result<ComRef<I>, HResult>
where
C: Class,
I: Interface,
{
create_instance::<C, I>(CLSCTX_INPROC_SERVER)
}
#[macro_export]
macro_rules! com_call {
($obj:expr, $interface:ident :: $method:ident ( $($arg:expr),* )) => {{
use $crate::error::ResultExt;
$crate::error::succeeded_or_err({
let obj: &$interface = &*$obj;
obj.$method($($arg),*)
}).function(concat!(stringify!($interface), "::", stringify!($method)))
.file_line(file!(), line!())
}};
($obj:expr, $interface:ident :: $method:ident ( $($arg:expr),+ , )) => {{
$crate::com_call!($obj, $interface::$method($($arg),+))
}};
}
pub fn get<I, F>(getter: F) -> Result<ComRef<I>, HResult>
where
I: Interface,
F: FnOnce(*mut *mut I) -> HRESULT,
{
let mut interface: *mut I = ptr::null_mut();
let hr = succeeded_or_err(getter(&mut interface as *mut *mut I))?;
NonNull::new(interface)
.map(|interface| unsafe { ComRef::from_raw(interface) })
.ok_or_else(|| HResult::new(hr))
}
#[macro_export]
macro_rules! com_call_getter {
(| $outparam:ident | $obj:expr, $interface:ident :: $method:ident ( $($arg:expr),* )) => {{
use $crate::error::ResultExt;
let obj: &$interface = &*$obj;
$crate::com::get(|$outparam| {
obj.$method($($arg),*)
}).function(concat!(stringify!($interface), "::", stringify!($method)))
.file_line(file!(), line!())
}};
(| $outparam:ident | $obj:expr, $interface:ident :: $method:ident ( $($arg:expr),+ , )) => {
$crate::com_call_getter!(|$outparam| $obj, $interface::$method($($arg),+))
};
}
pub fn get_cotaskmem<F, T>(getter: F) -> Result<CoTaskMem<T>, HResult>
where
F: FnOnce(*mut *mut T) -> HRESULT,
{
let mut ptr = ptr::null_mut() as *mut T;
let hr = succeeded_or_err(getter(&mut ptr))?;
NonNull::new(ptr)
.map(|ptr| unsafe { CoTaskMem::new(ptr) })
.ok_or_else(|| HResult::new(hr))
}
#[macro_export]
macro_rules! com_call_taskmem_getter {
(| $outparam:ident | $obj:expr, $interface:ident :: $method:ident ( $($arg:expr),* )) => {{
use $crate::error::ResultExt;
$crate::com::get_cotaskmem(|$outparam| {
$obj.$method($($arg),*)
}).function(concat!(stringify!($interface), "::", stringify!($method)))
.file_line(file!(), line!())
}};
(| $outparam:ident | $obj:expr, $interface:ident :: $method:ident ( $($arg:expr),+ , )) => {
$crate::com_call_taskmem_getter!(|$outparam| $obj, $interface::$method($($arg),+))
};
}
#[repr(transparent)]
#[derive(Debug)]
pub struct CoTaskMem<T>(NonNull<T>);
impl<T> CoTaskMem<T> {
pub unsafe fn new(p: NonNull<T>) -> CoTaskMem<T> {
CoTaskMem(p)
}
pub fn as_raw(&self) -> NonNull<T> {
self.0
}
pub fn as_raw_ptr(&self) -> *mut T {
self.0.as_ptr()
}
}
impl<T> Drop for CoTaskMem<T> {
fn drop(&mut self) {
unsafe {
CoTaskMemFree(self.0.as_ptr() as LPVOID);
}
}
}