microsoft / CsWinRT

C# language projection for the Windows Runtime
MIT License
539 stars 102 forks source link

Implement interop with .NET source generated `GeneratedComInterface` interfaces #1337

Closed jkoritzinsky closed 1 month ago

jkoritzinsky commented 1 year ago

Proposal: Implement interop with .NET source generated GeneratedComInterface interfaces

Summary

In .NET 8, the .NET team has introduced source-generated COM through the GeneratedComInterface attribute and a corresponding source-generator. Currently, CsWinRT supports interop with [ComImport] interfaces through the .As() method. This mechanism as it exists today will not work with GeneratedComInterface attributes.

However, CsWinRT could extend the IWinRTObject interface to implement the contract interface (IUnmanagedVirtualMethodTableProvider) that GeneratedComInterface uses to resolve the this pointer and vtable and use the StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy to resolve the implementation types from the public interface types. This would allow for seamless interop for WinRT types that also implement COM interfaces defined with GeneratedComInterface.

Rationale

Important Notes

This issue only covers RCW-based interop between the two systems. CCW-based interop would be more difficult and may not be as easy to do (and as a result is not included in the scope of this issue).

Open Questions

Sergio0694 commented 1 year ago

"CCW-based interop would be more difficult and may not be as easy to do"

What would that be like, hypothetically? CCW support is also something that's needed in a lot of scenarios. For instance, if you want to roll your own IBuffer implementation, or eg. to properly implement custom Win2D effects. I assume this would allow replacing all the handrwitten code (like this) with the new COM generators, but it wouldn't make it AOT friendly per se? I mean, this scenario isn't using [ComImport] already, no? 🤔

lhak commented 1 year ago

It would be very nice to have this functionality to support interfaces like IDataTransferManagerInterop in a trim-friendly way. I did a quick test and updated the AsInterface() method in ObjectReference.cs to check for the GeneratedComInterfaceAttribute and call StrategyBasedComWrappers.GetOrCreateObjectForComInstance() instead of Marshal.GetObjectForIUnknown(). This seems to work fine but I do not know if this is the right way to implement this functionality.

Sergio0694 commented 1 year ago

Well, I'd say regardless of whether that specific implementation is the same, for sure we'll need to remove that Marshal.GetObjectForIUnknown call, as that's completely not supported on NativeAOT anyway (as is [ComImport]) 😅

tipa commented 10 months ago

It it possible to generate trim/AOT-safe COMWrapper by hand in .NET8 with these source generators currently? Like @lhak I try to get DataTransferManager to work but no luck so far.

lhak commented 3 months ago

Is this supposed to work with the latest cswinrt preview release? I tried to access the IMemoryBufferByteAccess interface of a IMemoryBufferReference object, but a simple cast or calling reference.As() results in an invalid cast exception.

dongle-the-gadget commented 3 months ago

Are you sure that the object implements that interface (with the correct IID and everything)? It will throw that exception if QueryInterface returns an invalid cast error code.

lhak commented 3 months ago

@dongle-the-gadget Yes, I currently use code based on a helper class with the [WindowsRuntimeType] attribute and StrategyBasedComWrappers to not rely on the built-in com support. I also tried calling Marshal.QueryInterface() on the ((IWinRTObject)obj).NativeObject.ThisPtr pointer with the IMemoryBufferByteAccess iid and this also works fine.

dongle-the-gadget commented 3 months ago

Don’t use StrategyBasedComWrappers, that won’t work.

Cast the WinRT object to the object keyword then cast that to the interface you want. There should be a unit test in the AOT branch that demonstrates generated COM interface usage.

Scratch that, seems like C#/WinRT did not build the .NET 8 target, which is required for this functionality.