CsWinRT icon indicating copy to clipboard operation
CsWinRT copied to clipboard

Add support for CloakedIid<T> for WinRT/COM interfaces

Open Sergio0694 opened this issue 2 years ago • 2 comments

Proposal: support CloakedIid<T> for WinRT/COM interfaces

Summary

When defining a WinRT/COM class, WRL exposes the CloakedIid<T> helper, which allows interfaces to be hidden and only show up when explicitly queried for. That is, they wouldn't contribute to the list of implemented interfaces from IInspectable::GetIids. This is particularly common and useful for interop interfaces. Right now there's no way to express this with CsWinRT, as even using interface interfaces only makes it so the interface isn't explicitly visible in metadata, but if you retrieve a CCW for the object and call GetIids, those GUIDs would still show up. For instance, Win2D uses this pattern extensively (all the interop interfaces are implemented by the public WinRT objects as having a cloaked iid), but there's no way to replicate it with types authored in C#.

The proposal is to have a new attribute that would be recognized by the CsWinRT architecture, and would cause the corresponding interface to be skipped when computing the list of IIDs to return from the generated GetIids method from IInspectable:

namespace WinRT.Interop;

[AttributeUsage(AttributeTargets.AttributeTargets.Interface, Inherited = false, AllowMultiple = false)]
public sealed class CloakedIidAttribute : Attribute
{
}

Rationale

  • Allow CCWs defined in C# with CsWinRT to have parity with CloakedIid<T> from WRL

Use example

Consider the following declarations:

// Some custom type
public sealed class MyType : MyInterface
{
}

// Some custom interface
[Guid("SOME_GUID")]
[CloakedIid]
[WindowsRuntimeType]
[WindowsRuntimeHelperType(typeof(Interface))]
public interface MyInterface
{
    [Guid("SOME_GUID")]
    public struct Vftbl
    {
        public static readonly IntPtr AbiToProjectionVftablePtr = InitVtbl();

        private static IntPtr InitVtbl()
        {
            Vftbl* lpVtbl = (Vftbl*)ComWrappersSupport.AllocateVtableMemory(typeof(Vftbl), sizeof(Vftbl));

            lpVtbl->IUnknownVftbl = IUnknownVftbl.AbiToProjectionVftbl;

            return (IntPtr)lpVtbl;
        }

        internal IUnknownVftbl IUnknownVftbl;
    }
}

You would then get:

MyType myType = new();

// Get the CCW for the custom type
IntPtr myTypePtr = MarshalInspectable<MyType>.FromManaged(myType);

Guid myInterfaceIid = new Guid("SOME_GUID");

// Get the iids
int iidCount;
IntPtr iids;
IInspectable.Vftbl.AbiToProjectionVftable.GetIids(myTypePtr, &iidCount, &iids);

// This should print false
Console.WriteLine(new Span<Guid>((void*)iids, iidCount).Contains(myInterfaceIid));

// QI for the hidden interface
int result = Marshal.QueryInterface(myTypePtr, ref myInterfaceIid, out IntPtr myInterfacePtr);

// This should print 0
Console.WriteLine(result);

Open Questions

Not 100% clear to me whether this would be entirely on CsWinRT, or whether this should require any support from the ComWrappers side? If so, let me know whether I should move this to the runtime repo, or whether this is the right place for it 🙂

Also, the proposed API would make the cloaked iid feature opt-in per interface type. Should we consider a way to make this opt-in per concrete type on a given target interface, mirroring what WRL does? That seems more complex and potentially less trimmer friendly?

cc. @AaronRobinsonMSFT @jkoritzinsky @manodasanW

Sergio0694 avatar Mar 26 '23 11:03 Sergio0694

@Sergio0694 To make I understand the proposal, basically we want to be able to author types in C# (either using authoring support or even just a C# implemented class that is passed for an interface) and have it implement interfaces some of which you would be able to QI for if you know the IID but it will not get reported in GetIIDs calls.

In the Win2D scenario, I assume these interfaces are not represented in metadata either, or are they?

manodasanW avatar Mar 29 '23 23:03 manodasanW

Yup, that's exactly right! For Win2D, this is used for several interop interfaces, which are only declared either in the published header for Win2D or in some headers from the Windows SDK, but which are not part of metadata, yes. That is, in Win2D, those interfaces are only implemented by the type implementing the public WinRT interfaces, but they are not declared in IDL (so they also don't result in any projections being generated).

For instance, the public CanvasDevice type in Win2D (or rather, the implementation class for the public ICanvasDevice interface) uses CloakedIid to implement ID2D1DeviceContextPool, ICanvasResourceWrapper and IDxgiInterfaceAccess, which are all just COM interfaces defined in the Win2D/D2D headers. For custom effects, those have to implement the ICanvasImageInterop interface, also defined in the published header. You can get that to work today with CsWinRT (see #1283) through a custom interface with the right marshalling stubs to work with CsWinRT; but even if you make that interface internal, which makes it not leak through your managed API surface in .NET, it's still not really hidden at the ABI layer, as someone getting a CCW for that object and calling IInspectable::GetIIds will still see that IID. Using [CloakedIid] instead, we'd be able to make that interop interface "invisible", which is how you'd also implement it if you were authoring the custom effect in C++.

Sergio0694 avatar Mar 30 '23 09:03 Sergio0694