Skip to content

基于AOT的WinUI界面插件系统 #8

@BlameTwo

Description

@BlameTwo

原理

使用COM机制来加载调用插件,并在宿主程序中对插件进行加载与释放。

方法

接口定义

[GeneratedComInterface]
[Guid("C71CB0B9-3AB9-44C5-851E-A25C7025FE42")]
public partial interface IPlugin
{
    /// <summary>
    /// 插件名称
    /// </summary>
    /// <returns></returns>
    [return: MarshalAs(UnmanagedType.BStr)]
    string GetPluginName();

    /// <summary>
    /// 插件版本
    /// </summary>
    /// <returns></returns>
    [return: MarshalAs(UnmanagedType.BStr)]
    string GetPluginVersion();

    /// <summary>
    /// 插件是否在运行
    /// </summary>
    /// <returns></returns>
    [return: MarshalAs(UnmanagedType.Bool)]
    bool IsPluginRun();

    /// <summary>
    /// 显示UI
    /// </summary>
    void ShowUI();

    //其他内容
}

COM Server声明

public static class ComWrappersHelper
{
    public static StrategyBasedComWrappers Wrappers { get; } = new();
}

COM Client 声明

public static class ComWrappersHelper
{
    public static StrategyBasedComWrappers Wrappers { get; } = new();
}

COM之间传递指针

// Client
//在插件中实现,统一创建插件并返回指针对象,创建可导出模块显示
private const int S_OK = 0;
private const int E_POINTER = unchecked((int)0x80004003);
private const int E_FAIL = unchecked((int)0x80004005);
private const int E_INVALIDARG = unchecked((int)0x80070057);


[UnmanagedCallersOnly(EntryPoint = "CreatePluginFactory")]
public static int CreatePluginFactory(nint* factory)
{
    if (factory is null)
    {
        return E_POINTER;
    }

    try
    {
        *factory = ComWrappersHelper.Wrappers.GetOrCreateComInterfaceForObject(
            new PluginClassFactory(),
            CreateComInterfaceFlags.None);

        return S_OK;
    }
    catch (COMException ex)
    {
        *factory = 0;
        return ex.HResult;
    }
    catch
    {
        *factory = 0;
        return E_FAIL;
    }
}

加载插件

// 指定插件模块的入口函数
const string CreatePluginFactoryExportName = "CreatePluginFactory";
// 拿到COM加载器
StrategyBasedComWrappers wrappers = ComWrappersHelper.Wrppers;
nint modelHaldle = 0;
nint factoryPointer = 0;
//PluginEventSink eventSink = new();
// 通过Load的方式来加载插件,返回一个uint的指针数据
modelHaldle = NativeLibrary.Load(path);
//通过此指针来定位到入口函数处
nint export = NativeLibrary.GetExport(modelHaldle, CreatePluginFactoryExportName);
//获得入口函数指针位置
int result = CreatePluginFactory(export, out factoryPointer);
// 检查是否错误
Marshal.ThrowExceptionForHR(result);
// 通过COM加载器强转类型
IPluginFactory factoryProxy = (IPluginFactory)wrappers.GetOrCreateObjectForComInstance(factoryPointer, CreateObjectFlags.None);
// 创建插件本体
IPlugin plugin = factoryProxy.CreatePlugin(string.Empty);
Console.WriteLine(plugin.GetPluginName());
//显示UI,Markup方式
plugin.ShowUI();
//释放插件
NativeLibrary.Free(modelHaldle);


public unsafe int CreatePluginFactory(nint export, out nint factoryPointer)
{
    // 调用插件内实现的CreatePluginFactory入口方法,注意此方法仅为工厂类的实现,可换其他实现
    delegate* unmanaged<nint*, int> createPluginFactory = (delegate* unmanaged<nint*, int>)export;
    nint localFactoryPointer = 0;
    int hresult = createPluginFactory(&localFactoryPointer);
    factoryPointer = localFactoryPointer;
    return hresult;
}
···


插件定义示例
``` C#
[GeneratedComClass]
public partial class NowTimerPlugin : IPlugin
{
    private const string DiagnosticLogPath = @"D:\WorkSpace\ComPlugin\TestPlugin.ShowUI.log";

    [return: MarshalAs(UnmanagedType.BStr)]
    public string GetPluginName()
    {
        return "获取现在时间的插件";
    }

    [return: MarshalAs(UnmanagedType.BStr)]
    public string GetPluginVersion()
    {
        return "获取现在插件的版本";
    }

    [return: MarshalAs(UnmanagedType.Bool)]
    public bool IsPluginRun()
    {
        return true;
    }

    public void ShowUI()
    {
        try
        {
            Window win = new Window
            {
                Content = new TextBlock
                {
                    Text = $"现在时间: {DateTime.Now}",
                    FontSize = 24,
                    HorizontalAlignment = HorizontalAlignment.Center,
                    VerticalAlignment = VerticalAlignment.Center
                }
            };
            win.Activate();
        }
        catch (Exception ex)
        {
            File.AppendAllText(DiagnosticLogPath, "Outer catch:" + Environment.NewLine + ex + Environment.NewLine);
            Debug.WriteLine(ex);
            throw;
        }
    }
}

以上方法来自于目前的测试结果,此方法目前未能测试到使用Resource加载资源对象,目前来看,可以作为一些小的插件进行开发。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions