-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
In #33485, we wired up the ComWrappers API to be called first-chance from the Marshal APIs and from the IL stub marshalers. This has started to prove a problem for CsWinRT, since there's no way to determine when to use the ComWrappers' implementation and when to use the runtime's built-in APIs.
In particular, the CsWinRT ComWrappers implementation of ComputeVtables has to be able to handle all types, including types that do not implement any WinRT APIs, to correctly implement WinRT semantics for WinUI. Additionally, since WinUI depends on the reference tracking support from ComWrappers, the CsWinRT ComWrappers implementation has to be registered as the global implementation. As a result, if someone such as WPF calls Marshal.GetIUnknownForObject, this call will go down the ComWrappers route first if the Windows SDK is also being used. WPF is expecting to get the built-in runtime-implemented CCWs, but by default CsWinRT would provide one instead.
This proves to be a problem when the user (either WPF or someone else directly using the Marshal APIs or using COM objects in P/Invoke signatures) is trying to pass a managed implementations of COM APIs to native. CsWinRT's ComWrappers only support the WinRT model (as it should be, they shouldn't have to completely re-implement the runtime's CCW/RCW implementation), so they would create a WinRT CCW of the managed object.
We could attempt to use a heuristic in the CsWinRT ComWrappers implementation to determine whether to use the ComWrappers or just return null and fall back, but that heuristic may be tricky or downright impossible to get exactly right. I've included an example below where the heuristic is extremely difficult if not impossible:
Let's take a class class Foo : System.Collections.IEnumerable { ... } and a P/Invoke signature void Bar(System.Collections.IEnumerable ienum). In the WinRT world, we would want to treat this as a Microsoft.UI.Xaml.Interop.IBindableIterable. However, the built-in CCW system would generate an IDispatch implementation that exposes the GetEnumerator method as DISPID_NEWENUM.
Since there is no way to determine from within a ComWrappers implementation if it is being called from the Marshal APIs or a P/Invoke marshaler (without attempting to just look up the call stack), the CsWinRT ComWrappers would never be able to know if it should marshal an instance of Foo itself or if it should let the runtime marshal it.
There is a corresponding problem for the RCW direction, but using a heuristic of "does this object implement IInspectable" works well as a heuristic for ComWrappers.
There are a few possible solutions here:
- Provide an additional flag on the
CreateObjectFlagsandCreateComInterfaceFlagsenumeration to denote when the ComWrappers methods are being called from theMarshalAPIs or from an IL stub. - Remove the support wiring up a ComWrappers instance as a first-chance handler for the Marshal APIs or from an IL stub.
I'm also open to additional ideas.