对于Windows App SDK应用程序,要提供Windows小部件,需要根据官方文档编写一些COM互操作代码:
首先,实现一个类工厂,它将根据请求实例化 WidgetProvider:
// 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;
}
}
// 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);
}
我想在我的应用程序中使用 AOT。但是,一旦我在项目中启用了 AOT (
<PublishAot>true</PublishAot>
),第二个代码块中的 CoRegisterClassObject
调用将显示警告:
P/调用方法'CoRegisterClassObject(Guid, Object, Ulnt32, Ulnt32, out UInt32)' 使用 COM 编组声明一个参数。的正确性 修剪后无法保证 COM 互操作。接口和 接口成员可能会被删除。
并在执行时抛出异常:
System.NotSupportedException:“内置 COM 已通过 功能开关。请参阅 https://aka.ms/dotnet-illink/com 了解更多信息 信息。”
根据此链接(已知的修剪不兼容性),这种 COM 互操作代码与 .NET 修剪和 AOT 不兼容,替代方法是使用 COM Wrappers 或其源生成:ComWrappers 的源生成。
源代码生成方法似乎更简单。但对于一个对 COM 一无所知的 .NET 开发人员来说,阅读文档并找出如何重写代码仍然很困难。
感谢任何可以尝试解决此问题的人!
这里的代码替换了程序的主代码和 COM 类工厂,并且与 .NET 8 AOT(因此禁用运行时编组)和较新的 ComWrappers 源代码生成兼容:
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";
}
}
还有我的.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>
一些注释(也在代码中突出显示为注释):
ERROR_BAD_FORMAT
(0x8007000B
) 错误,在真实的命名空间中声明一个真实的类“Program”。LibraryImport
代替 DllImport
。[MarshalAs(UnmanagedType.IUnknown)] object pUnk
参数,而是使用原始 IntPtr
(对于 IUnknown
)。GeneratedComInterface
和 GeneratedComClass
(并使它们部分化,以便源生成器可以完成其工作)