Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize IUnknown identity checks #3073

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion crates/libs/core/src/unknown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,17 @@ impl Drop for IUnknown {

impl PartialEq for IUnknown {
fn eq(&self, other: &Self) -> bool {
// First we test for ordinary pointer equality. If two COM interface pointers have the
// same pointer value, then they are the same object. This can save us a lot of time,
// since calling QueryInterface is much more expensive than a single pointer comparison.
//
// However, interface pointers may have different values and yet point to the same object.
// Since COM objects may implement multiple interfaces, COM identity can only
// be determined by querying for `IUnknown` explicitly and then comparing the
// pointer values. This works since `QueryInterface` is required to return
// the same pointer value for queries for `IUnknown`.
self.cast::<IUnknown>().unwrap().0 == other.cast::<IUnknown>().unwrap().0
self.as_raw() == other.as_raw()
sivadeilra marked this conversation as resolved.
Show resolved Hide resolved
|| self.cast::<IUnknown>().unwrap().0 == other.cast::<IUnknown>().unwrap().0
}
}

Expand Down
79 changes: 75 additions & 4 deletions crates/tests/implement_core/src/com_object.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Unit tests for `windows_core::ComObject`

use core::ffi::c_void;
use std::borrow::Borrow;
use std::sync::atomic::{AtomicBool, Ordering::SeqCst};
use std::sync::Arc;
Expand Down Expand Up @@ -30,6 +31,8 @@ unsafe trait IBar2: IBar {

const APP_SIGNATURE: [u8; 8] = *b"cafef00d";

// We are intentionally declaring IBar twice (in two different interface chains).
// If you change this, you will need to update tests.
#[implement(IFoo, IBar, IBar2)]
struct MyApp {
// We use signature to verify field offsets for dynamic casts
Expand Down Expand Up @@ -332,7 +335,7 @@ fn construct_with_into() {
}

#[test]
fn debug() {
fn com_object_debug() {
let app = MyApp::new(100);
let s = format!("{:?}", app);
assert_eq!(s, "x = 100");
Expand Down Expand Up @@ -417,12 +420,80 @@ fn common_method_name() {
}

#[test]
fn debug_fmt() {
fn interface_debug_fmt() {
let app = MyApp::new(42);

let iunknown: IUnknown = app.to_interface();
let unknown_dbg = format!("{iunknown:?}");
assert!(unknown_dbg.starts_with("IUnknown(0x"), "{unknown_dbg:?}");

let ifoo: IFoo = app.to_interface();
let foo_dbg = format!("{ifoo:?}");
assert!(foo_dbg.starts_with("IFoo(0x"), "{foo_dbg:?}");
}

// Test that we always get the same IUnknown pointer for an object, regardless of which
// interface we use to query for it.
#[test]
fn iunknown_identity() {
let app = MyApp::new(0);

println!("identity = {:?}", &app.identity as *const _);
println!("vtables.0 = {:?}", &app.vtables.0 as *const _);
println!("vtables.1 = {:?}", &app.vtables.1 as *const _);
println!("vtables.2 = {:?}", &app.vtables.2 as *const _);
sivadeilra marked this conversation as resolved.
Show resolved Hide resolved

let raw_identity: *mut c_void = &app.identity as *const _ as *mut c_void;
let raw_foo: *mut c_void = &app.vtables.0 as *const _ as *mut c_void;
let raw_bar: *mut c_void = &app.vtables.1 as *const _ as *mut c_void;
let raw_bar2: *mut c_void = &app.vtables.2 as *const _ as *mut c_void;

// iunknown is from the identity vtable slot. It is the canonical IUnknown pointer.
let iunknown: IUnknown = app.to_interface();
println!("IUnknown = {iunknown:?}");
assert_eq!(
iunknown.as_raw(),
raw_identity,
"IUnknown should come from primary interface"
);

// Get the most-derived interface of each interface chain.
let ifoo: IFoo = app.to_interface();
println!("IFoo = {ifoo:?}");
let ibar: IBar = app.to_interface();
let ibar2: IBar2 = app.to_interface();

assert_eq!(ifoo.as_raw(), raw_foo, "IFoo interface chain");
assert_eq!(ibar.as_raw(), raw_bar, "IBar interface chain");
assert_eq!(ibar2.as_raw(), raw_bar2, "IBar2 interface chain");

// Do a static cast from IBar to IUnknown and verify that the interface pointer for the
// resulting IUnknown is _not_ the same as the canonical IUnknown pointer.
{
let ibar_iunknown_static: IUnknown = (*ibar).clone();
assert_ne!(
ibar_iunknown_static.as_raw(),
iunknown.as_raw(),
"IBar-to-IUnknown is non-canonical interface chain"
);

// The equality implementation uses QueryInterface to check that they are the same pointer.
assert_eq!(
ibar_iunknown_static, iunknown,
"QueryInterface for IUnknown yields same pointer"
);
}

// ibar and ibar2_ibar point to different interface chains, even though they have the same
// COM interface type.
let ibar2_ibar: &IBar = &ibar2; // static cast
assert_ne!(
ibar.as_raw(),
ibar2_ibar.as_raw(),
"IBar from different interface chains have different pointer values"
);
assert_eq!(
ibar, *ibar2_ibar,
"IBar from different interface chains are equal (using QueryInterface)"
);
}

// This tests that we can place a type that is not Send in a ComObject.
Expand Down