How to rewrite the Windows App SDK WidgetProvider registration code using ComWrappers source generation?
See the question and my original answer on StackOverflowHere's a code that replaces the program's main code and COM class factory and is compatible with .NET 8 AOT (so with runtime marshalling disabled) and the newer ComWrappers source generation:
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using Microsoft.Windows.Widgets.Providers;
using WinRT;
// AOT: declare we disable runtime marshalling to enable early compilation errors
[assembly: DisableRuntimeMarshalling]
namespace ConsoleApp;
// AOT, COM Source wrapper generator: don't use implicit root class or face unexpected ERROR_BAD_FORMAT exceptions
internal partial class Program
{
// AOT: use LibraryImport
[LibraryImport("kernel32")]
public static partial IntPtr GetConsoleWindow();
// AOT: don't use 'object' for IUnknown parameters
[LibraryImport("ole32")]
public static partial int CoRegisterClassObject(in Guid rclsid, IntPtr pUnk, uint dwClsContext, uint flags, out uint lpdwRegister);
[LibraryImport("ole32")]
public static partial int CoRevokeClassObject(uint dwRegister);
static void Main()
{
Console.WriteLine("Registering Widget Provider");
// ask for the widget provider's IUnknown pointer
var provider = new WidgetProviderFactory<WidgetProvider>();
var comWrappers = new StrategyBasedComWrappers();
var unk = comWrappers.GetOrCreateComInterfaceForObject(provider, CreateComInterfaceFlags.None);
Guid CLSID_Factory = Guid.Parse("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX");
CoRegisterClassObject(CLSID_Factory, unk, 0x4, 0x1, out var 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);
}
}
// AOT: use GeneratedComInterface
[GeneratedComInterface, Guid(Guids.IClassFactory)]
public partial interface IClassFactory
{
[PreserveSig]
int CreateInstance(IntPtr pUnkOuter, in Guid riid, out IntPtr ppvObject);
// AOT: mark .NET's bool as UnmanagedType.Bool (Win32 BOOL)
[PreserveSig]
int LockServer([MarshalAs(UnmanagedType.Bool)] bool fLock);
}
// AOT: use GeneratedComClass
[GeneratedComClass]
public partial class WidgetProviderFactory<T> : IClassFactory where T : IWidgetProvider, new()
{
public int CreateInstance(IntPtr pUnkOuter, in 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(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) => 0;
private const int CLASS_E_NOAGGREGATION = -2147221232;
private const int E_NOINTERFACE = -2147467262;
}
static class Guids
{
public const string IClassFactory = "00000001-0000-0000-C000-000000000046";
public const string IUnknown = "00000000-0000-0000-C000-000000000046";
}
}
And my .csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0-windows10.0.26100</TargetFramework>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<PublishAot>true</PublishAot>
<IsAotCompatible>true</IsAotCompatible>
<RuntimeIdentifiers>win-x64</RuntimeIdentifiers>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.6.240923002" />
</ItemGroup>
</Project>
Few remarks (also highlighted in code as comments):
- Don't use the implicit/top level program structure (like in the provided sample) for AOT compilation. This causes unexpected
ERROR_BAD_FORMAT
(0x8007000B
) errors at runtime on first COM call like CoRegisterClassObject (although it seems to compile ok...), declare a real class like the usual "Program" for example in a real namespace. - declare assembly: DisableRuntimeMarshalling so you'll catch error at compilation time and avoid later surprises.
- Use LibraryImport instead of
DllImport
(and methods declaration must bepartial
instead ofextern
) - Don't use
[MarshalAs(UnmanagedType.IUnknown)] object pUnk
parameters but use rawIntPtr
instead (forIUnknown
). - Use GeneratedComInterface and GeneratedComClass on COM interfaces and COM classes (and make them partial so the source generator can do its job)