Open smourier opened 2 months ago
cc @Sergio0694, I think you've tried this before?
I don't think we support scenarios with a [GenerateComClass]
type that also implements WinRT interfaces. That said, we do support mixed WinRT/COM scenarios (I added support for that as it's needed by ComputeSharp for Win2D interop), but it's a bit clunky to setup and not really documented. You can see an example here. You basically need to:
<TYPE_NAME>Methods
type in the right ABI.*
namespace (see link)[WindowsRuntimeType]
and [WindowsRuntimeHelperType]
(see here)AbiToProjectionVftablePtr
property of the *Methods
type from aboveThen you can just implement this interface in your class, and things should work as expected. That is, if you get a CCW from your object, that will also have the correct vtable slot entry for your COM interface, so native callers can use it.
Thanks, it's not super simple but at least there's a way. IMHO, you could document this, it's interesting to know we can use C#/WinRT this "manual" way.
FWIW, here is my Program.cs modified this way and GetGeometry
is now called:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using MyNamespace;
using Windows.Graphics;
using Windows.UI.Composition;
using WinRT;
using WinRT.Interop;
[assembly: DisableRuntimeMarshalling]
namespace ConsoleAotAuthor
{
internal class Program
{
static void Main()
{
var geo = new Geo();
var compositor = new CompositionPath(geo);
}
}
public partial class Geo : IGeometrySource2D, IGeometrySource2DInterop
{
public unsafe int GetGeometry(out nint value) => throw new NotImplementedException();
public unsafe int TryGetGeometryUsingFactory(nint factory, out nint value) => throw new NotImplementedException();
}
}
namespace ABI.MyNamespace
{
// all names here are hardcoded in WinRT's generator (ABI., Methods, IID, AbiToProjectionVftablePtr)
[EditorBrowsable(EditorBrowsableState.Never)]
public static class IGeometrySource2DInteropMethods
{
public static Guid IID { get; } = typeof(IGeometrySource2DInterop).GUID;
public static nint AbiToProjectionVftablePtr { get; } = IGeometrySource2DInterop.Vftbl.InitVtbl();
}
}
namespace MyNamespace
{
[Guid("0657af73-53fd-47cf-84ff-c8492d2a80a3")]
[WindowsRuntimeType]
//[WindowsRuntimeHelperType(typeof(IGeometrySource2DInterop))] // this seems optional
public interface IGeometrySource2DInterop
{
// interface (public) declaration
// from %ProgramFiles(x86)%\Windows Kits\10\Include\10.0.26100.0\winrt\windows.graphics.interop.h
int GetGeometry(out nint value);
int TryGetGeometryUsingFactory(nint factory, out nint value);
// v-table
internal unsafe struct Vftbl
{
public static nint InitVtbl()
{
var lpVtbl = (Vftbl*)ComWrappersSupport.AllocateVtableMemory(typeof(Vftbl), sizeof(Vftbl));
lpVtbl->IUnknownVftbl = IUnknownVftbl.AbiToProjectionVftbl;
lpVtbl->GetGeometry = &GetGeometryFromAbi;
lpVtbl->TryGetGeometryUsingFactory = &TryGetGeometryUsingFactoryFromAbi;
return (nint)lpVtbl;
}
private IUnknownVftbl IUnknownVftbl;
// interface delegates
private delegate* unmanaged[MemberFunction]<nint, nint*, int> GetGeometry;
private delegate* unmanaged[MemberFunction]<nint, nint, nint*, int> TryGetGeometryUsingFactory;
// interface implementation
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
private static int GetGeometryFromAbi(nint thisPtr, nint* value)
{
try
{
var hr = ComWrappersSupport.FindObject<IGeometrySource2DInterop>(thisPtr).GetGeometry(out var v);
*value = v;
return hr;
}
catch (Exception e)
{
ExceptionHelpers.SetErrorInfo(e);
return Marshal.GetHRForException(e);
}
}
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
private static int TryGetGeometryUsingFactoryFromAbi(nint thisPtr, nint factory, nint* value)
{
try
{
var hr = ComWrappersSupport.FindObject<IGeometrySource2DInterop>(thisPtr).TryGetGeometryUsingFactory(factory, out var v);
*value = v;
return hr;
}
catch (Exception e)
{
ExceptionHelpers.SetErrorInfo(e);
return Marshal.GetHRForException(e);
}
}
}
}
}
Describe the bug With AOT, GeneratedComClasses and GeneratedComInterfaces and with runtime marshaling disabled, I'm trying to create a COM class that derives from a C#/WinRT generated interface, for example IGeometrySource2D (it's the same for all interfaces but I tried to choose a simple one) and it doesn't work, it throws:
Unhandled exception. System.ArgumentException: The parameter is incorrect. Invalid argument to parameter source. Object parameter must not be null. at WinRT.ExceptionHelpers.<ThrowExceptionForHR>g__Throw|38_0(Int32 hr) at WinRT.ExceptionHelpers.ThrowExceptionForHR(Int32 hr)
I've tried more complex implementations, for example with
ICustomQueryInterface
(seeGeo2
class below) since it's based onIntPtr
instead of .NET objects, but I can't find a way that works.Note that I need this class to also implement a non WinRT, IUnknown-derived class (here
IGeometrySource2DInterop
) declared manually.Is there a way to make this work?
To Reproduce Here's my .csproj:
<PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0-windows10.0.26100.0</TargetFramework> <Nullable>enable</Nullable> <PublishAot>true</PublishAot> <InvariantGlobalization>true</InvariantGlobalization> <WindowsSdkPackageVersion>10.0.26100.42</WindowsSdkPackageVersion> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.1.1" /> </ItemGroup> </Project>
Here's my Progam.cs:
using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; using Windows.Graphics; using Windows.UI.Composition; using WinRT; [assembly: DisableRuntimeMarshalling] namespace ConsoleAotAuthor; internal class Program { static void Main() { var geo = new Geo(); // or Geo2 (it's worse) var compositor = new CompositionPath(geo); } } [GeneratedComClass] public partial class Geo : IGeometrySource2D, IGeometrySource2DInterop { // these are never called anyway public int GetGeometry(out nint value) => throw new NotImplementedException(); public int TryGetGeometryUsingFactory(nint factory, out nint value) => throw new NotImplementedException(); } [GeneratedComClass] public partial class Geo2 : IGeometrySource2D, IGeometrySource2DInterop, ICustomQueryInterface { // these are never called anyway public int GetGeometry(out nint value) => throw new NotImplementedException(); public int TryGetGeometryUsingFactory(nint factory, out nint value) => throw new NotImplementedException(); CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out nint ppv) { if (iid == typeof(IGeometrySource2D).GUID) { ppv = MarshalInspectable<IGeometrySource2D>.FromManaged(this); return CustomQueryInterfaceResult.Handled; } // what to return here??? else if (iid == typeof(IGeometrySource2DInterop).GUID) { ComWrappers.TryGetComInstance(this, out var unk); // unk is 0 // returning this causes System.ExecutionEngineException "Fatal error. Internal CLR error. (0x80131506)" later on ppv = new StrategyBasedComWrappers().GetOrCreateComInterfaceForObject(this, CreateComInterfaceFlags.None); return CustomQueryInterfaceResult.Handled; } ppv = 0; return CustomQueryInterfaceResult.NotHandled; } } // from %ProgramFiles(x86)%\Windows Kits\10\Include\10.0.26100.0\winrt\windows.graphics.interop.h [GeneratedComInterface, Guid("0657af73-53fd-47cf-84ff-c8492d2a80a3")] public partial interface IGeometrySource2DInterop { [PreserveSig] int GetGeometry(out nint value); [PreserveSig] int TryGetGeometryUsingFactory(nint factory, out nint value); }
Expected behavior It works with older C#/WinRT and w/o AOT, I can do something like
var unk = Marshal.GetIUnknownForObject(this); return WinRT.MarshalInspectable<Windows.Graphics.IGeometrySource2D>.FromAbi(unk);
But
Marshal.GetIUnknownForObject
cannot be used with disabled runtime marshaling, that I also require for proper AOT support.
Here's an example code I'm trying to use with GeneratedComInterface and WindowsRuntimeType, which works with IGraphicsEffectD2D1Interop and is primarily used to display the mica and DesktopAcrylic background colors
这是我尝试将 GeneratedComInterface 和 WindowsRuntimeType 配合使用的一个示例代码,该代码作用于 IGraphicsEffectD2D1Interop,主要用于显示 Mica 和 DesktopAcrylic 背景色
public static unsafe class IGraphicsEffectD2D1InteropMethods
{
public static Guid IID { get; } = typeof(IGraphicsEffectD2D1Interop).GUID;
public static IntPtr AbiToProjectionVftablePtr { get; } = (nint)StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy.GetIUnknownDerivedDetails(typeof(IGraphicsEffectD2D1Interop).TypeHandle).ManagedVirtualMethodTable;
}
[GeneratedComInterface, WindowsRuntimeType, Guid("2FC57384-A068-44D7-A331-30982FCF7177")]
public partial interface IGraphicsEffectD2D1Interop
{
[PreserveSig]
int GetEffectId(out Guid id);
[PreserveSig]
int GetNamedPropertyMapping(IntPtr name, out uint index, out GRAPHICS_EFFECT_PROPERTY_MAPPING mapping);
[PreserveSig]
int GetPropertyCount(out uint count);
[PreserveSig]
int GetProperty(uint index, out IntPtr value);
[PreserveSig]
int GetSource(uint index, out IntPtr source);
[PreserveSig]
int GetSourceCount(out uint count);
}
I don't think we support scenarios with a
[GenerateComClass]
type that also implements WinRT interfaces. That said, we do support mixed WinRT/COM scenarios (I added support for that as it's needed by ComputeSharp for Win2D interop), but it's a bit clunky to setup and not really documented. You can see an example here. You basically need to:
- Declare the correct
<TYPE_NAME>Methods
type in the rightABI.*
namespace (see link)- Implement your interface with
[WindowsRuntimeType]
and[WindowsRuntimeHelperType]
(see here)- Implement a managed vftable for it (see the same link)
- Return that from the
AbiToProjectionVftablePtr
property of the*Methods
type from aboveThen you can just implement this interface in your class, and things should work as expected. That is, if you get a CCW from your object, that will also have the correct vtable slot entry for your COM interface, so native callers can use it.
I tried a new way to use the GeneratedComInterface and WindowsRuntimeType properties in a single COM interface. You'll need to manually modify the ManagedVirtualMethodTable generated by AbiToProjectionVftablePtr for the Com source in IXXXMethods
我尝试了一种新的方法可以将 GeneratedComInterface 和 WindowsRuntimeType 这两个属性在一个 COM 接口中使用。需要在 IXXXMethods 中手动修改 AbiToProjectionVftablePtr 为 Com 源生成的 ManagedVirtualMethodTable。
效果图 / Results
Describe the bug With AOT, GeneratedComClasses and GeneratedComInterfaces and with runtime marshaling disabled, I'm trying to create a COM class that derives from a C#/WinRT generated interface, for example IGeometrySource2D (it's the same for all interfaces but I tried to choose a simple one) and it doesn't work, it throws:
I've tried more complex implementations, for example with
ICustomQueryInterface
(seeGeo2
class below) since it's based onIntPtr
instead of .NET objects, but I can't find a way that works.Note that I need this class to also implement a non WinRT, IUnknown-derived class (here
IGeometrySource2DInterop
) declared manually.Is there a way to make this work?
To Reproduce Here's my .csproj:
Here's my Progam.cs:
Expected behavior It works with older C#/WinRT and w/o AOT, I can do something like
But
Marshal.GetIUnknownForObject
cannot be used with disabled runtime marshaling, that I also require for proper AOT support.