1

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!

3
  • This specific piece of code could work as is. Your question is too wide and covers too much code (the widget provider is big). Have you tried to build your project as AOT with runtime marshaling disabled? If yes, what errors do you get exactly? Commented Oct 24, 2024 at 6:09
  • Sorry for missing some information. I have modified the details. The problem is not related to C# implementation of WidgetProvider. It's on the two blocks of COM interop code. Specifically, the call to CoRegisterClassObject will throw exceptions with AOT enabled. Commented Oct 24, 2024 at 7:54
  • From my understanding, to use COM source generation, the IClassFactory interface in the first code should be decorated with [GeneratedComInterface], and the WidgetProviderFactory<T> class should be decorated with [GeneratedComClass], and the call to CoRegisterClassObject should be CoRegisterClassObject(new StrategyBasedComWrappers().GetOrCreateComInterfaceForObject(new WidgetProviderFactory<WidgetProvider>(), CreateComInterfaceFlags.None)). But these modifications are not enough as they can't be compiled. @SimonMourier Commented Oct 24, 2024 at 8:00

1 Answer 1

2

Update 2025/11/16 I have put a full .NET 10 version here https://github.com/smourier/ExampleWidgetProviderAot

Here's a code that replaces the program's main code and COM class factory and is compatible with .NET AOT (so with runtime marshalling disabled) and the newer ComWrappers source generation:

internal partial class Program
{
    static void Main()
    {
        Console.WriteLine("Registering Widget Provider");

        var factory = new WidgetProviderFactory();
        var wrappers = new StrategyBasedComWrappers();
        var wrappersUnk = wrappers.GetOrCreateComInterfaceForObject(factory, CreateComInterfaceFlags.None);
        var hr = CoRegisterClassObject(typeof(WidgetProvider).GUID, wrappersUnk, CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, out var cookie);
        if (hr < 0)
        {
            Console.WriteLine($"Failed to register class object. HR=0x{hr:X8}");
            return;
        }

        Console.WriteLine("Registered successfully. Press ENTER to exit.");
        Console.ReadLine();

        // Wait until the manager has disposed of the last widget provider.
        using (var emptyWidgetListEvent = WidgetProvider.GetEmptyWidgetListEvent())
        {
            Console.WriteLine("Waiting for all widgets to be removed...");
            emptyWidgetListEvent.WaitOne();
        }

        Console.WriteLine("No more widgets. Exiting.");
        _ = CoRevokeClassObject(cookie);
    }

#pragma warning disable IDE1006 // Naming Styles

    private const uint CLSCTX_LOCAL_SERVER = 4;
    private const uint REGCLS_MULTIPLEUSE = 1;

#pragma warning restore IDE1006 // Naming Styles

    [LibraryImport("OLE32")]
    [PreserveSig]
    private static partial int CoRegisterClassObject(in Guid rclsid, nint pUnk, uint dwClsContext, uint flags, out uint lpdwRegister);

    [LibraryImport("OLE32")]
    [PreserveSig]
    private static partial int CoRevokeClassObject(uint dwRegister);
}

[GeneratedComClass]
public partial class WidgetProviderFactory : IClassFactory
{
#pragma warning disable IDE1006 // Naming Styles

    private const int CLASS_E_NOAGGREGATION = unchecked((int)0x80040110);
    private const int E_NOINTERFACE = unchecked((int)0x80004002);

#pragma warning restore IDE1006 // Naming Styles

    int IClassFactory.LockServer(bool fLock) => 0;
    int IClassFactory.CreateInstance(nint pUnkOuter, in Guid riid, out nint ppvObject)
    {
        ppvObject = 0;
        if (pUnkOuter != 0)
            return CLASS_E_NOAGGREGATION;

        if (riid == typeof(WidgetProvider).GUID || riid == typeof(IUnknown).GUID)
        {
            ppvObject = MarshalInspectable<IWidgetProvider>.FromManaged(new WidgetProvider());
            return 0;
        }
        return E_NOINTERFACE;
    }
}

[GeneratedComInterface, Guid("00000001-0000-0000-c000-000000000046")]
public partial interface IClassFactory
{
    [PreserveSig]
    int CreateInstance(nint pUnkOuter, in Guid riid, out nint ppvObject);

    [PreserveSig]
    int LockServer([MarshalAs(UnmanagedType.Bool)] bool fLock);
}

// just for the GUID
[GeneratedComInterface, Guid("00000000-0000-0000-C000-000000000046")]
public partial interface IUnknown { }

public class CompactWidgetInfo { ... } // same as in in article

// TODO: build you own GUID
[Guid("a73cac52-e431-420d-9759-d510dfb2524e")]
public partial class WidgetProvider : IWidgetProvider { ... } // same as in in article

And my .csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0-windows10.0.26100</TargetFramework>
    <Nullable>enable</Nullable>
    <PublishAot>true</PublishAot>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    <InvariantGlobalization>true</InvariantGlobalization>
    <DisableRuntimeMarshalling>true</DisableRuntimeMarshalling>
    <CsWin32RunAsBuildTask>true</CsWin32RunAsBuildTask>
    <Platforms>ARM64;x64;x86;x86</Platforms>
    <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
  </PropertyGroup>
  
  <ItemGroup>
    <Using Include="Microsoft.Windows.Widgets.Providers" />
    <Using Include="System" />
    <Using Include="System.Collections.Generic" />
    <Using Include="System.Runtime.InteropServices" />
    <Using Include="System.Runtime.InteropServices.Marshalling" />
    <Using Include="System.Threading" />
    <Using Include="WinRT" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
  </ItemGroup>

</Project>

Few remarks:

  • 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 or equivalent in csproj, so you'll catch error at compilation time and avoid later surprises.
  • Use LibraryImport instead of DllImport (and methods declaration must be partial instead of extern)
  • Don't use [MarshalAs(UnmanagedType.IUnknown)] object pUnk parameters but use raw IntPtr instead (for IUnknown).
  • Use GeneratedComInterface and GeneratedComClass on COM interfaces and COM classes (and make them partial so the source generator can do its job)
  • With GeneratedComInterface, you cannot use generic classes, so WidgetProviderFactory is not generic any more
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.