For Windows App SDK apps, to provide Windows widgets, it's required to write some COM interop code according to the official documentation:
First, Implement a class factory that will instantiate WidgetProvider on request:
// FactoryHelper.cs
using Microsoft.Windows.Widgets.Providers;
using System.Runtime.InteropServices;
using WinRT;
namespace COM
{
static class Guids
{
public const string IClassFactory = "00000001-0000-0000-C000-000000000046";
public const string IUnknown = "00000000-0000-0000-C000-000000000046";
}
///
/// IClassFactory declaration
///
[ComImport, ComVisible(false), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid(COM.Guids.IClassFactory)]
internal interface IClassFactory
{
[PreserveSig]
int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject);
[PreserveSig]
int LockServer(bool fLock);
}
[ComVisible(true)]
class WidgetProviderFactory<T> : IClassFactory
where T : IWidgetProvider, new()
{
public int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject)
{
ppvObject = IntPtr.Zero;
if (pUnkOuter != IntPtr.Zero)
{
Marshal.ThrowExceptionForHR(CLASS_E_NOAGGREGATION);
}
if (riid == typeof(T).GUID || riid == Guid.Parse(COM.Guids.IUnknown))
{
// Create the instance of the .NET object
ppvObject = MarshalInspectable<IWidgetProvider>.FromManaged(new T());
}
else
{
// The object that ppvObject points to does not support the
// interface identified by riid.
Marshal.ThrowExceptionForHR(E_NOINTERFACE);
}
return 0;
}
int IClassFactory.LockServer(bool fLock)
{
return 0;
}
private const int CLASS_E_NOAGGREGATION = -2147221232;
private const int E_NOINTERFACE = -2147467262;
}
}
Then, Register the widget provider class object with OLE:
// Program.cs
using System.Runtime.InteropServices;
using ComTypes = System.Runtime.InteropServices.ComTypes;
using Microsoft.Windows.Widgets;
using ExampleWidgetProvider;
using COM;
using System;
[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();
[DllImport("ole32.dll")]
static extern int CoRegisterClassObject(
[MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
[MarshalAs(UnmanagedType.IUnknown)] object pUnk,
uint dwClsContext,
uint flags,
out uint lpdwRegister);
[DllImport("ole32.dll")] static extern int CoRevokeClassObject(uint dwRegister);
Console.WriteLine("Registering Widget Provider");
uint cookie;
Guid CLSID_Factory = Guid.Parse("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX");
CoRegisterClassObject(CLSID_Factory, new WidgetProviderFactory<WidgetProvider>(), 0x4, 0x1, out cookie);
Console.WriteLine("Registered successfully. Press ENTER to exit.");
Console.ReadLine();
if (GetConsoleWindow() != IntPtr.Zero)
{
Console.WriteLine("Registered successfully. Press ENTER to exit.");
Console.ReadLine();
}
else
{
// Wait until the manager has disposed of the last widget provider.
using (var emptyWidgetListEvent = WidgetProvider.GetEmptyWidgetListEvent())
{
emptyWidgetListEvent.WaitOne();
}
CoRevokeClassObject(cookie);
}
I want to use AOT in my app. But once I enabled AOT in my project (<PublishAot>true</PublishAot>), the CoRegisterClassObject call in the second block of code will show a warning:
P/invoke method 'CoRegisterClassObject(Guid, Object, Ulnt32, Ulnt32, out UInt32)' declares a parameter with COM marshalling. Correctness of COM interop cannot be guaranteed after trimming. Interfaces and interface members might be removed.
and throw an exception when excuting:
System.NotSupportedException: “Built-in COM has been disabled via a feature switch. See https://aka.ms/dotnet-illink/com for more information.”
Accoding to this link (Known trimming incompatibilities), this kind of COM interop code is incompatible with .NET trimming and AOT, and the alternative approach is to use COM Wrappers or its source generation: Source generation for ComWrappers.
The source generation approach seems to be simpler. But for a .NET developer who knows nothing about COM, it's still hard to read the documentation and find out how to rewrite the code.
Thanks for anyone who can try to solve this!
CoRegisterClassObjectwill throw exceptions with AOT enabled.IClassFactoryinterface in the first code should be decorated with[GeneratedComInterface], and theWidgetProviderFactory<T>class should be decorated with[GeneratedComClass], and the call toCoRegisterClassObjectshould beCoRegisterClassObject(new StrategyBasedComWrappers().GetOrCreateComInterfaceForObject(new WidgetProviderFactory<WidgetProvider>(), CreateComInterfaceFlags.None)). But these modifications are not enough as they can't be compiled. @SimonMourier