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

COM interface impls move from MyApp to MyApp_Impl ("outer" object) #3065

Merged
merged 3 commits into from
Jun 13, 2024

Conversation

sivadeilra
Copy link
Collaborator

@sivadeilra sivadeilra commented Jun 3, 2024

This PR requires COM objects to implement COM interfaces on the "outer" (MyApp_Impl) type, which is generated by the #[implement] macro, instead of the user-defined (or "inner") (MyApp) type. This is important because many COM objects need access to the full COM object, not just the contents of the wrapped "inner" type.

For example, COM objects may need to cast themselves to a particular IFoo that they implement and then return that IFoo pointer as an output parameter of a COM method call. This is not possible in the current design because COM objects receive method calls that are typed &self : T where T is the inner type, so they do not have access to the vtables or reference count of the current COM object.

This is a breaking change to all code that uses the #[implement] macro. All code in this pattern:

#[implement(IFoo)]
struct MyApp { ... }

impl IFoo_Impl for MyApp { ... }

will need to be changed to this:

#[implement(IFoo)]
struct MyApp { ... }

impl IFoo_Impl for MyApp_Impl { ... }

@sivadeilra sivadeilra requested a review from dpaoliello June 3, 2024 08:28
crates/tests/implement_core/src/impl_on_outer.rs Outdated Show resolved Hide resolved
crates/libs/implement/src/lib.rs Outdated Show resolved Hide resolved
crates/tests/implement_core/src/impl_on_outer.rs Outdated Show resolved Hide resolved
@sivadeilra sivadeilra force-pushed the user/ardavis/implement-outer branch from 508bd93 to ccf95f8 Compare June 3, 2024 19:48
@sivadeilra sivadeilra requested a review from dpaoliello June 3, 2024 22:38
@sivadeilra sivadeilra marked this pull request as ready for review June 3, 2024 22:43
dpaoliello
dpaoliello previously approved these changes Jun 3, 2024
@kennykerr
Copy link
Collaborator

More fundamentally, the problem with the existing implementation is that certain functions - like casting/querying for an interface implemented by the implementation - assume that the type that the implement macro is attached to is already boxed and there's no safe/sound way to panic if and when that's not the case. This PR aims to solve that nicely and it should just be "the way" to implement COM objects in Rust. So,

  1. I don't think its sufficient to offer an optional alternative.
  2. There should just be one way to do this and that should be simplest and most streamlined default rather than something you need to opt in to.
  3. Even if both approaches were sound, having two non-complimentary ways to do essentially the same thing seems very confusing.
  4. I don't like the ergonomics of the sample in the PR description even if you remove the @ Outer thing.
#[implement(IFoo)]
struct MyApp { ... }

impl IFoo_Impl for MyApp_Impl { ... }

There's no hint that IFoo_Impl is even a name to be reckoned with. That's more a problem with the interface definition, but MyApp_Impl is likewise something you'd have to "know" a priori. Even impl IFoo::Impl for MyApp::Impl would be less mysterious and name-mangily. Maybe something else entirely. I'll play around with it over the next few days and see if I can come up with something a bit more natural. If we are to make a change here, I want to be confident that we are on a good path before we disrupt existing code.

@sivadeilra
Copy link
Collaborator Author

sivadeilra commented Jun 5, 2024

The more I think about it, I think we should just flip to MyApp_Impl for COM interface implementations and be done with it. Yes, it's a breaking change, but windows-rs has made breaking changes before, and the major version number is still 0.

Edit: Actually, I just thought of something. What about this?

impl IFoo_Impl for ComObject<MyApp> {
   ...
}

This actually compiles. I haven't tried wiring up the method dispatch code to it, but this might be a good approach.

@sivadeilra
Copy link
Collaborator Author

Ok, I implemented support for placing interface impls on ComObject<T> instead of on T or T_Impl. It looks nice, especially from the POV of the developer implementing a COM object, because ComObject<MyApp> gives developers an "go to docs" thing, and it gives them an easy way to discover the methods that are in-scope for self.

I created a draft PR, #3071 , just for discussion. That PR still has the "inner vs. outer" option on #[implement], and I'm leaning toward "this needs to just be a breaking change". So either 1) we go with the opt-in, or 2) we go with the breaking change. In either case, I'll update this PR after the discussion, to reflect the final design, rather than #3071.

@kennykerr
Copy link
Collaborator

I like this!

impl IFoo_Impl for ComObject<MyApp> {
   ...
}

@sivadeilra
Copy link
Collaborator Author

I like this!

Me too! ... Unfortunately, I tried taking that all the way to completion, and ran into an unsolvable problem with generics and trait implementations. It's not going to work. :(

@sivadeilra sivadeilra force-pushed the user/ardavis/implement-outer branch from 021fd1e to bc86afc Compare June 12, 2024 20:52
@sivadeilra sivadeilra changed the title Basic implementation of impl-on-outer COM interface impls move from MyApp to MyApp_Impl ("outer" object) Jun 12, 2024
Comment on lines +63 to +64
self.current
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this just formatting?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. These files weren't being run through rustfmt before.

Comment on lines +16 to +21
use windows_core::IUnknownImpl;
Ok(windows_core::ComObject::new(StockIterator {
owner: self.to_object(),
current: 0.into(),
})
.into_interface())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder, can we implement Into/From here as a shorthand for the simple case of returning the interface as before - then it would be similar to before: Ok(StockIterator {...}.into()) ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The into() thing still totally works. I just like the ComObject::new(...).into_interface() more because it's more explicit.

where
T: windows_core::RuntimeType,
T::Default: Clone,
{
fn Current(&self) -> windows_core::Result<T> {
let owner: &StockIterable<T> = unsafe { windows_core::AsImpl::as_impl(&self.owner) };
let owner: &StockIterable<T> = &self.owner;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much nicer.

@@ -85,6 +88,6 @@ where
type Error = windows_core::Error;
fn try_from(values: Vec<T::Default>) -> windows_core::Result<Self> {
// TODO: should provide a fallible try_into or more explicit allocator
Ok(StockIterable { values }.into())
Ok(windows_core::ComObject::new(StockIterable { values }).into_interface())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, be nice if this simple case had a shorthand.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It still works; just being explicit. I can change this back, but I like the explicitness.

@kennykerr kennykerr merged commit a8bb33c into microsoft:master Jun 13, 2024
91 checks passed
@kennykerr
Copy link
Collaborator

kennykerr commented Jun 13, 2024

Looks like a new Rust nightly update is failing the merge build workflow. #3096

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants