如何使用ComWrappers源码生成重写Windows App SDK WidgetProvider注册代码?

问题描述 投票:0回答:1

对于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;

    }
}

然后,用OLE注册widget提供者类对象:

// 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 开发人员来说,阅读文档并找出如何重写代码仍然很困难。

感谢任何可以尝试解决此问题的人!

c# .net com windows-app-sdk native-aot
1个回答
0
投票

这里的代码替换了程序的主代码和 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>

一些注释(也在代码中突出显示为注释):

  • 不要使用隐式/顶级程序结构(如提供的示例中所示)进行 AOT 编译。这会导致意外的
    ERROR_BAD_FORMAT
    (
    0x8007000B
    ) 错误,在真实的命名空间中声明一个真实的类“Program”。
  • 声明 程序集:DisableRuntimeMarshalling,这样您就可以在编译时捕获错误并避免以后出现意外。
  • 使用
    LibraryImport
    代替
    DllImport
  • 不要使用
    [MarshalAs(UnmanagedType.IUnknown)] object pUnk
    参数,而是使用原始
    IntPtr
    (对于
    IUnknown
    )。
  • 在 COM 接口和 COM 类上使用
    GeneratedComInterface
    GeneratedComClass
    (并使它们部分化,以便源生成器可以完成其工作)
© www.soinside.com 2019 - 2024. All rights reserved.