use std::ffi::c_void;
use std::marker::PhantomData;
use std::sync::atomic::{fence, AtomicPtr, AtomicU32, Ordering};
use crate::{ComPtr, Guid, IUnknownVtbl, Interface, RawHResult, E_NOINTERFACE, E_POINTER, S_OK};
pub unsafe trait ImplementedBy<T>: Interface {
const VTBL: &'static Self::Vtbl;
}
#[repr(C)]
pub struct ComObject<T> {
vtbl: *const c_void,
shadow: *const Shadow<T>,
}
pub struct InterfaceEntry<T> {
vtbl: *const c_void,
matches_iid: fn(&Guid) -> bool,
_value: PhantomData<fn(T)>,
}
impl<T> InterfaceEntry<T> {
pub fn of<I: ImplementedBy<T>>() -> InterfaceEntry<T> {
InterfaceEntry { vtbl: I::VTBL as *const I::Vtbl as *const c_void, matches_iid: I::matches_iid, _value: PhantomData }
}
}
struct Shadow<T> {
refs: AtomicU32,
wrappers: Box<[Wrapper<T>]>,
inner: AtomicPtr<c_void>,
value: T,
}
struct Wrapper<T> {
object: ComObject<T>,
matches_iid: fn(&Guid) -> bool,
}
impl<T> ComObject<T> {
#[inline]
pub unsafe fn value<'a>(this: *mut c_void) -> &'a T {
&(*(*(this as *const ComObject<T>)).shadow).value
}
pub unsafe fn ref_count(this: *mut c_void) -> u32 {
(*(*(this as *const ComObject<T>)).shadow).refs.load(Ordering::Relaxed)
}
}
fn new_object<T>(value: T, interfaces: impl Iterator<Item = InterfaceEntry<T>>) -> *mut ComObject<T> {
let wrappers: Box<[Wrapper<T>]> = interfaces
.map(|entry| Wrapper {
object: ComObject { vtbl: entry.vtbl, shadow: std::ptr::null() },
matches_iid: entry.matches_iid,
})
.collect();
let shadow = Box::into_raw(Box::new(Shadow {
refs: AtomicU32::new(1),
wrappers,
inner: AtomicPtr::new(std::ptr::null_mut()),
value,
}));
unsafe {
for wrapper in (*shadow).wrappers.iter_mut() {
wrapper.object.shadow = shadow;
}
&raw mut (*shadow).wrappers[0].object
}
}
pub fn make_com<I, T>(value: T) -> ComPtr<I>
where
I: ImplementedBy<T>,
T: 'static,
{
make_com_with(value, [])
}
pub fn make_com_with<I, T, const N: usize>(value: T, more: [InterfaceEntry<T>; N]) -> ComPtr<I>
where
I: ImplementedBy<T>,
T: 'static,
{
let first = new_object(value, std::iter::once(InterfaceEntry::of::<I>()).chain(more));
unsafe { ComPtr::from_raw(first as *mut I).unwrap_unchecked() }
}
pub unsafe fn set_inner<T>(this: *mut c_void, inner: *mut c_void) {
let shadow = (*(this as *const ComObject<T>)).shadow;
let earlier = (*shadow).inner.swap(inner, Ordering::AcqRel);
if !earlier.is_null() {
(*(earlier as *const crate::IUnknown)).release();
}
}
pub struct UnknownThunks<I, T>(PhantomData<(I, T)>);
impl<I: Interface, T> UnknownThunks<I, T> {
pub const VTBL: IUnknownVtbl = IUnknownVtbl {
query_interface: Self::query_interface,
add_ref: Self::add_ref,
release: Self::release,
};
unsafe extern "system" fn query_interface(
this: *mut c_void,
riid: *const Guid,
ppv: *mut *mut c_void,
) -> RawHResult {
if ppv.is_null() || riid.is_null() {
return E_POINTER;
}
let shadow = (*(this as *const ComObject<T>)).shadow;
for wrapper in (*shadow).wrappers.iter() {
if (wrapper.matches_iid)(&*riid) {
Self::add_ref(this);
*ppv = &raw const wrapper.object as *mut c_void;
return S_OK;
}
}
let inner = (*shadow).inner.load(Ordering::Acquire);
if !inner.is_null() {
return (*(inner as *const crate::IUnknown)).query_interface_raw(&*riid, ppv);
}
*ppv = std::ptr::null_mut();
E_NOINTERFACE
}
unsafe extern "system" fn add_ref(this: *mut c_void) -> u32 {
let shadow = &*(*(this as *const ComObject<T>)).shadow;
shadow.refs.fetch_add(1, Ordering::Relaxed) + 1
}
unsafe extern "system" fn release(this: *mut c_void) -> u32 {
let shadow = (*(this as *const ComObject<T>)).shadow as *mut Shadow<T>;
let left = (*shadow).refs.fetch_sub(1, Ordering::Release) - 1;
if left == 0 {
fence(Ordering::Acquire);
let inner = (*shadow).inner.swap(std::ptr::null_mut(), Ordering::AcqRel);
if !inner.is_null() {
(*shadow).refs.store(1, Ordering::Relaxed);
(*(inner as *const crate::IUnknown)).release();
}
drop(Box::from_raw(shadow));
}
left
}
}
impl IUnknownVtbl {
pub const fn new<I: Interface, T>() -> Self {
UnknownThunks::<I, T>::VTBL
}
}
unsafe impl<T> ImplementedBy<T> for crate::IUnknown {
const VTBL: &'static IUnknownVtbl = &IUnknownVtbl::new::<crate::IUnknown, T>();
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{IUnknown, E_NOINTERFACE};
use std::rc::Rc;
use std::cell::Cell;
struct DropFlag(Rc<Cell<bool>>);
impl Drop for DropFlag {
fn drop(&mut self) {
self.0.set(true);
}
}
#[test]
fn refcount_and_query_interface() {
let dropped = Rc::new(Cell::new(false));
let p: ComPtr<IUnknown> = make_com(DropFlag(dropped.clone()));
let raw = p.as_ptr() as *mut c_void;
unsafe {
assert_eq!(ComObject::<DropFlag>::ref_count(raw), 1);
let q = p.clone();
assert_eq!(ComObject::<DropFlag>::ref_count(raw), 2);
let r = q.cast::<IUnknown>().unwrap();
assert_eq!(ComObject::<DropFlag>::ref_count(raw), 3);
drop(r);
drop(q);
assert_eq!(ComObject::<DropFlag>::ref_count(raw), 1);
let mut out = std::ptr::null_mut();
let bogus = Guid::from_u128(0x1234);
assert_eq!(p.query_interface_raw(&bogus, &mut out), E_NOINTERFACE);
assert!(out.is_null());
}
assert!(!dropped.get());
drop(p);
assert!(dropped.get());
}
macro_rules! test_interface {
($name:ident, $vtbl:ident, $imp:ident, $method:ident, $iid:expr) => {
#[repr(C)]
struct $name {
vtbl: *const $vtbl,
}
#[repr(C)]
struct $vtbl {
base: IUnknownVtbl,
$method: unsafe extern "system" fn(this: *mut c_void) -> u32,
}
trait $imp {
fn $method(&self) -> u32;
}
unsafe impl Interface for $name {
const IID: Guid = Guid::from_u128($iid);
const NAME: &'static str = stringify!($name);
type Vtbl = $vtbl;
fn matches_iid(iid: &Guid) -> bool {
*iid == Self::IID || IUnknown::matches_iid(iid)
}
}
impl $name {
fn $method(&self) -> u32 {
unsafe { ((*self.vtbl).$method)(self as *const _ as *mut c_void) }
}
}
unsafe impl<T: $imp> ImplementedBy<T> for $name {
const VTBL: &'static $vtbl = &$vtbl {
base: IUnknownVtbl::new::<$name, T>(),
$method: {
unsafe extern "system" fn thunk<T: $imp>(this: *mut c_void) -> u32 {
<T as $imp>::$method(ComObject::<T>::value(this))
}
thunk::<T>
},
};
}
};
}
test_interface!(IFirst, IFirstVtbl, IFirstImpl, first, 0x11111111_0000_0000_0000_000000000001);
test_interface!(ISecond, ISecondVtbl, ISecondImpl, second, 0x22222222_0000_0000_0000_000000000002);
test_interface!(IThird, IThirdVtbl, IThirdImpl, third, 0x33333333_0000_0000_0000_000000000003);
struct Both(u32, #[allow(dead_code)] DropFlag);
impl IFirstImpl for Both {
fn first(&self) -> u32 {
self.0 + 1
}
}
impl ISecondImpl for Both {
fn second(&self) -> u32 {
self.0 + 2
}
}
impl IThirdImpl for Both {
fn third(&self) -> u32 {
self.0 + 3
}
}
#[test]
fn an_object_with_several_interfaces_answers_for_each_of_them() {
let dropped = Rc::new(Cell::new(false));
let first: ComPtr<IFirst> =
make_com_with(Both(40, DropFlag(dropped.clone())), [InterfaceEntry::of::<ISecond>(), InterfaceEntry::of::<IThird>()]);
let raw = first.as_ptr() as *mut c_void;
assert_eq!(41, first.first());
let second = first.cast::<ISecond>().unwrap();
assert_eq!(42, second.second());
let third = second.cast::<IThird>().unwrap();
assert_eq!(43, third.third());
let back = third.cast::<IFirst>().unwrap();
assert_eq!(41, back.first());
assert_eq!(back.as_ptr(), first.as_ptr());
assert_ne!(second.as_ptr() as *mut c_void, raw);
assert_ne!(third.as_ptr() as *mut c_void, second.as_ptr() as *mut c_void);
unsafe {
assert_eq!(4, ComObject::<Both>::ref_count(raw));
assert_eq!(4, ComObject::<Both>::ref_count(third.as_ptr() as *mut c_void));
}
let unknowns = [
first.cast::<IUnknown>().unwrap(),
second.cast::<IUnknown>().unwrap(),
third.cast::<IUnknown>().unwrap(),
];
assert!(unknowns.iter().all(|unknown| unknown.as_ptr() as *mut c_void == raw));
drop(unknowns);
let mut out = std::ptr::null_mut();
unsafe {
assert_eq!(second.as_unknown().query_interface_raw(&Guid::from_u128(0x1234), &mut out), E_NOINTERFACE);
}
assert!(out.is_null());
drop(first);
drop(back);
drop(third);
assert!(!dropped.get());
assert_eq!(42, second.second());
drop(second);
assert!(dropped.get());
}
#[test]
fn an_object_with_one_interface_does_not_answer_for_another() {
let dropped = Rc::new(Cell::new(false));
let first: ComPtr<IFirst> = make_com(Both(1, DropFlag(dropped.clone())));
assert_eq!(2, first.first());
assert_eq!(Err(crate::HResult(E_NOINTERFACE)), first.cast::<ISecond>().map(|_| ()));
let unknown = first.cast::<IUnknown>().unwrap();
assert_eq!(unknown.as_ptr() as *mut c_void, first.as_ptr() as *mut c_void);
drop(first);
assert!(!dropped.get());
drop(unknown);
assert!(dropped.get());
}
struct Inner(u32, #[allow(dead_code)] DropFlag);
impl ISecondImpl for Inner {
fn second(&self) -> u32 {
self.0
}
}
#[test]
fn a_composed_object_asks_its_inner_object_and_releases_it() {
let outer_dropped = Rc::new(Cell::new(false));
let inner_dropped = Rc::new(Cell::new(false));
let outer: ComPtr<IFirst> = make_com(Both(1, DropFlag(outer_dropped.clone())));
assert_eq!(Err(crate::HResult(E_NOINTERFACE)), outer.cast::<ISecond>().map(|_| ()));
let inner: ComPtr<ISecond> = make_com(Inner(7, DropFlag(inner_dropped.clone())));
unsafe { set_inner::<Both>(outer.as_ptr() as *mut c_void, inner.into_raw() as *mut c_void) };
assert_eq!(2, outer.first());
let second = outer.cast::<ISecond>().unwrap();
assert_eq!(7, second.second());
assert_eq!(Err(crate::HResult(E_NOINTERFACE)), outer.cast::<IThird>().map(|_| ()));
drop(second);
drop(outer);
assert!(outer_dropped.get());
assert!(inner_dropped.get());
}
#[test]
fn guid_layout_and_parse() {
assert_eq!(std::mem::size_of::<Guid>(), 16);
let g = Guid::parse("809c652e-7396-11d2-9771-00a0c9b4d50c").unwrap();
assert_eq!(g, Guid::from_u128(0x809c652e_7396_11d2_9771_00a0c9b4d50c));
assert_eq!(g.to_string(), "809c652e-7396-11d2-9771-00a0c9b4d50c");
assert_eq!(g.as_com_h_materialized().data4, [0x97, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71]);
}
}