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

Fix invalid ComVisible attributes #3207

Merged
merged 1 commit into from
May 5, 2020
Merged

Conversation

hughbe
Copy link
Contributor

@hughbe hughbe commented May 4, 2020

Proposed Changes

  • Fix invalid ComVisible attributes

Fixes #1878

Microsoft Reviewers: Open in CodeFlow

@hughbe hughbe requested a review from a team as a code owner May 4, 2020 19:28
@ghost ghost assigned hughbe May 4, 2020
@@ -28,8 +28,7 @@ public interface IRawElementProviderFragment : IRawElementProviderSimple
/// </summary>
/// <param name="direction">Indicates the direction in which to navigate</param>
/// <returns>Returns the element in the specified direction</returns>
[return: MarshalAs(UnmanagedType.IUnknown)]
object /*IRawElementProviderFragment*/ Navigate(NavigateDirection direction);
IRawElementProviderFragment Navigate(NavigateDirection direction);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

See the definition in https://docs.microsoft.com/en-us/windows/win32/api/uiautomationcore/nf-uiautomationcore-irawelementproviderfragment-navigate

HRESULT Navigate(
  NavigateDirection direction ,
  IRawElementProviderFragment **pRetVal
);

Was the existing implementation incorrect? I think possibly technically because we'd return IUnknown rather than IRawElementProviderFragment. But I don't know enough interop to be able to say
/cc @JeremyKuhne @weltkante

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes I think this can be a bug if the IUnknown is not the one of IRawElementProviderFragment (which can happen if there are multiple interface vtables in play). The method may never have been used actively or you may have been lucky the CLR picks the right IUnknown. I don't think theres a guarantee it will pick the right one though if you don't specify it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll open an issue. This is why we should have stuff like the interop PR merged so we can yeet these bugs away

Copy link
Member

Choose a reason for hiding this comment

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

you may have been lucky the CLR picks the right IUnknown

How is that possible? It isn't querying for IUnknown- any COM pointer is IUnknown by definition. You'd just be getting the same pointer. I suppose if you then did the query interface and it gave you a different IRawElementProviderFragment than itself, but that would be fundamentally broken, no?

There is value in not creating the derived RCW from a perf perspective. If we don't ever cast the object to a given interface we can avoid a lot of overhead. This is particularly true if we can keep things as IntPtr and avoid even the IUnknown wrapper.

Copy link
Contributor Author

@hughbe hughbe May 4, 2020

Choose a reason for hiding this comment

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

So I'm thinking of the following example:

IRawElementProviderFragment *pFragment = ...;
IRawElementProviderFragment* navigateResult;
HRESULT hr = pFragment->Navigate(NavigateDirection_Parent, &navigateResult);
assert(hr == S_OK);

UiaRect boundingRect;
hr = result->get_BoundingRect(&boundingRect);
assert(hr == S_OK);

// Do something with boundingRect

In this case, we expect navigateResult to be of type IRawElementProviderFragment. However, we actually returned a value of type IUnknown from the managed code. Isn't this undefined behaviour?

Copy link
Contributor

@weltkante weltkante May 5, 2020

Choose a reason for hiding this comment

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

How is that possible?

I don't know how the framework implements IUnknown, its perfectly fine to reuse the vtable of one of the interfaces (as long as you always pick the same one). IRawElementProviderFragment inherits from IUnknown so its perfectly fine to return the pointer to that instead of creating two separate vtables.

If the framework doesn't return IRawElementProviderFragment and instead returns an unrelated subclass of IUnknown then you get either an access violation or call a random function in the same position of the vtable, but with wrong arguments.

There is value in not creating the derived RCW from a perf perspective. If we don't ever cast the object to a given interface we can avoid a lot of overhead.

This is about creating a CCW though, the COM vtable of an interface, you are returning a .NET object to native code.

There is also a lot of value in only ever creating one vtable if you already know beforehand that the class implements only one interface. If the interface doesn't end up being marshaled or anything else complicated then you might even be able to only ever create that single interface (happens often in native COM implementations). Not saying the framework does this particular optimization, just saying its a possibility.

Isn't this undefined behaviour?

Depends. Native code does that all the time, getting an IUnknown back from a call and directly casting it to a subclass interface, assuming knowledge about what the implementation really returns.

If the framework is consistent in picking the IUnknown implementation to be, say, the first interface (which happens to be IRawElementProviderFragment), then it might just work out. I'm not optimistic it does, just saying its a possibility things have worked out so far.

There is no reason to not fix that though.

Copy link
Member

Choose a reason for hiding this comment

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

If the framework is consistent in picking the IUnknown implementation to be, say, the first interface

Alright, I see what you're getting at. I was looking at the roundtripping of the RCW, which unwraps back to the native object. Passing AccessibleObject is going to give you a CCW for IUnknown itself I would presume.

Note that CCW that come back are also unwrapped to their managed object.

@hughbe hughbe force-pushed the UiaCore-ComVisible branch from 177ed2b to 087361c Compare May 4, 2020 19:35
@hughbe hughbe force-pushed the UiaCore-ComVisible branch from 087361c to 4d9d53d Compare May 4, 2020 19:35
@RussKie RussKie merged commit 423b2bc into dotnet:master May 5, 2020
@ghost ghost added this to the 5.0 Preview5 milestone May 5, 2020
@hughbe hughbe deleted the UiaCore-ComVisible branch May 5, 2020 22:52
@ghost ghost locked as resolved and limited conversation to collaborators Feb 1, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Review COM interface declarations for conflicting ComImport/ComVisible attributes
4 participants