Self-developed hot-swappable WPF Plug-in System (MSF)

Self-developed hot-swappable WPF Plug-in System (MSF)

The need for plug-in mainly stems from the pursuit of flexibility in software architecture. Especially when developing large, complex software systems or requiring continuous updates, plug-in can improve the scalability, customization, isolation, security of the software system. Ability to sex, maintainability, modularity, easy upgrades and updates, and support for third-party development, thereby meeting changing business needs and technical challenges.

最后更新 5/7/2024 11:15 PM
趋时软件
预计阅读 14 分钟
分类
WPF
标签
.NET WPF architecture design security plugin system

The need for plug-in mainly stems from the pursuit of flexibility in software architecture. Especially when developing large, complex software systems or requiring continuous updates, plug-in can improve the scalability, customization, isolation, security of the software system. Ability to sex, maintainability, modularity, easy upgrades and updates, and support for third-party development, thereby meeting changing business needs and technical challenges.

1. Plug-in exploration

In WPF, we usually have two options when we want to develop a plug-in program. One is MEF and the other is MAF. They have their own advantages and disadvantages. Let's analyze it below.

1.1. MEF(Managed Extensibility Framework)

1.1.1. advantages

  1. Easy to get started: It is relatively simple to use, and developers can define and export components through simple attribute tags without having to write a lot of complex code.

  2. Lightweight: MEF is a lightweight framework with low performance overhead.

  3. Low coupling: By splitting the application into multiple independent plug-ins, each responsible for implementing a specific function, coupling between modules is reduced. This makes the code easier to understand and maintain, while also reducing the risk of modifying one module having unintended effects on other modules.

  4. Parallel development: With MEF, different development teams can develop different plug-ins in parallel without worrying about dependencies between them. Each team can focus on its own functional implementation without having to wait for other teams to complete their work. This can significantly improve development efficiency.

  5. Easy to test and maintain: Since each plug-in is a separate unit, it can be tested and maintained individually. This reduces testing and maintenance complexity and allows problems to be located and resolved more quickly when they arise.

  6. Easy to extend new features: When you need to add new features, you just need to develop a new plug-in and add it to your application. This avoids the need for major modifications and recompilations of the entire application, shortening development cycles and reducing costs.

1.1.2. Disadvantages:

  1. Plug-in isolation: Plug-in isolation cannot be supported, which means that once a problem occurs with one of the plug-ins running, it will affect the entire application. It also cannot be hot-plugged and cannot dynamically update plug-ins while running.

  2. Life cycle: Plug-in life cycle management is not supported, and plug-in start and stop cannot be controlled in fine-grained.

1.2. MAF(Managed AddIn Framework)

Like the MEF plug-in, MAF also has the advantages of low coupling, parallel development, easy testing and maintenance, and easy expansion of new functions. Of course, it has some other advantages.

1.2.1. advantages

  1. Plug-in isolation: MAF supports plug-in isolation at the application domain and process level. Abnormal plug-in operation will not affect the entire application. When the plug-in needs to be updated, there is no need to restart the entire application.

  2. Life cycle: MAF provides complete life cycle management, which can control operations such as starting, stopping and uninstalling plug-ins.

  3. Plug-in version: MAF can support running multiple versions of a plug-in at the same time. This feature can enable dynamic rolling back of plug-in. Once a problem occurs with a new plug-in, it can be instantly rolled back to the old version.

1.2.2. disadvantages

  1. Complexity: The use and configuration of MAF are relatively complex. Developers need to understand concepts such as application domains, plug-in activation, and sandbox execution, and need to write appropriate code to manage the loading and unloading process of plug-ins.

  2. Performance overhead: Because each plug-in executes in a separate application domain, additional performance overhead may be incurred. Especially when loading a large number of plug-ins or frequently, it may affect the performance of the application.

1.3 summary

Through comparison, we have a basic understanding of the plug-in system. If there is no requirement for plug-ins to run in isolation, then MEF is a good choice. It is relatively simple and does not require understanding of complex theories. Refer to the example code, it can be quickly used in projects. If we need to build an application with higher security and better performance, then MAF is more appropriate, but MAF has some big problems. For example, even if you implement a very simple function, you must follow a fixed project structure. Implementation, poor flexibility, extremely complex to use, and a high threshold. When the application reaches a certain size, its program loading speed can be a problem. These shortcomings have led to the fact that only a handful of people choose it in actual project development.

For the above reasons, we need a plug-in system that combines the characteristics of MEF and MAF. It should be a lightweight framework with good performance, easy to use, strong scalability, safe and reliable features. This is the theme today.

2. system design

2.1. system architecture

图片

2.2. initiate process

图片

2.3. detailed design

2.3.1. container

Container is the core of the plug-in system. It provides services such as plug-in detection, plug-in loading, cross-process communication services, exception reporting, message forwarding, and plug-in life cycle management.

2.3.2. plug-in launcher

It is a console application that is responsible for the operation of plug-ins. It has functions such as loading plug-in configuration files, reporting plug-in exception information to containers, and plug-in hot plugging.

2.3.3. plug-in

插件是一个dll程序集或exe程序,该程序集或exe程序必须有一个类继承自Plugin抽象类,以供容器探测插件时被识别到。在插件类中可以定义自己的 UI(可以是任何FrameworkElement元素)或服务,以供容器调用。

3. example analysis

3.1. Configuration for container creation

// 创建一个容器
var container = new Container();
// 配置参数
container.Configure(options =>
{
    // 插件目录
    options.PluginDirectory = "Plugins";
    // 启动插件进程的超时时间
    options.PluginProcessTimeout = 6000;
    // 单个插件是否允许多开
    options.PluginAllowsMultipleInstances = false;
    // 是否启用热插拔
    options.IsEnableHotSwap = true;
    // 显示控制台
    options.IsShowConsole = false;
});
// 注册跨进程通讯服务
container.RegisterIpcService<RemotingService>();
// 插件错误处理
container.PluginError += Container_PluginError;
// 启动容器
container.Run();

3.2. Plug-in operation effect

图片

3.3. Multi-plug-in isolation operation

Each plug-in is a separate exe program after it is launched, and they run without affecting each other.

图片

3.4. Plug-in exception

When a plug-in exception occurs, the plug-in startup process will report the exception information to the container, which will uninstall the plug-in and give the host program the option of whether to restart the plug-in.

3.4.1. Throw exceptions manually

图片

3.4.2. divisor zero exception

图片

3.5. The plug-in process exited unexpectedly

The running status of the plug-in will be monitored throughout the container. If the plug-in process is found to have been accidentally terminated, the container will report the information to the host program, and the host program will decide whether to restart the plug-in.

图片

3.6. Hot plug in

3.6.1. New plug-ins discovered at runtime

默认只识别到了 4 个插件,从另一个文件夹中复制一个插件dll文件到插件目录以后会通知宿主程序发现了新插件,宿主程序可以决定是否要加载这个插件。

图片

3.6.2. Delete plug-ins at runtime

The container receives a notification when deleting a plug-in file, but it does not uninstall the plug-in immediately. Instead, it gives the option to the host program, which determines whether to uninstall the deleted plug-in. If the host does not want to uninstall, the deleted plug-in can continue to run without interruption.

图片

3.6.3. Run-time update plug-ins

Plug-in 1 has a white background, and the new version of plug-in 1 has a red background. When the old version is replaced with the new version, the container sends a notification to the host asking whether to replace the plug-in.

图片

3.7. plug-in communication

The plug-in communication part includes registration messages, receiving messages, and sending messages. The receiving and sending of messages only need to pay attention to the message type, regardless of who the sender and receiver are. As long as this type of message is registered, once there is a message of this type, you will receive a notification. Plugins can communicate not only with plugins, but also with hosts.

3.7.1. Registration message

以下代码注册一个类型为Notice的消息,并在注册方法中传入一个名为ReceiveMessages的回调方法,在该方法中处理消息接收。

plugin.ReregisterMessage<Notice>(ReceiveMessages);

3.7.2. receive messages

private void ReceiveMessages(Notice notice){
}

3.7.3. message sending

plugin.SendMessage(notice);

3.7.4. effect demonstration

图片

3.8. Prompt for plug-in not saving

Before closing the plug-in, the host can decide whether it can be closed based on the status of the plug-in. If there is unsaved work, the host can be notified to cancel closing the plug-in.

图片

3.9. The plug-in uses a separate App.config file

By default, each application can only load one configuration file with the same name as the application file name. Plug-ins can create their own application configuration files.

图片

App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="setting1" value="value1" />
    <add key="setting2" value="value2" />
  </appSettings>
</configuration>

** Operation effect **

图片

3.10. Multiple plug-ins open

A single plug-in allows multiple instances to run simultaneously and can be configured in container parameters.

图片

3.11. Run away from the host window

图片

3.12. Cross-process communication service extension

插件系统默认使用RemotingIpcChannel进行跨进程通讯,但是为了便于扩展,这里并没有直接把Ipc服务写进容器,而是采用了开放性的设计,如果不想使用IpcChannel,可以在创建容器以后注册自己的 Ipc 服务。

图片

4. Project actual combat

The following example shows the application of a plug-in system in a typical software with menus, toolbars, and documents. When the plug-in loads, the menus, toolbars, and documents in the plug-in will be loaded to the host programmer. When the plug-in is unexpectedly terminated or actively closed, the menus, toolbars, and documents in the plug-in will be automatically unloaded.

图片

Two commands have been added to the plug-in, the "Open" menu under the File menu and the "Document View" menu under the View. After clicking the menu, the command will be forwarded to the plug-in for execution.

private MSFCommand[] CreateCommands()
{
    var openCommand = new MSFCommand(() => MessageBox.Show("菜单"), () => true)
    {
        Id = Guid.NewGuid().ToString(),
        Name = "打开",
        Type = "Menu",
        Target = "MainWindow",
        Location = "文件(_F).打开(_O)",
        Order = 0
    };

    var editorViewCommand = new MSFCommand(() => MessageBox.Show("文档视图"))
    {
        Id = Guid.NewGuid().ToString(),
        Name = "文档视图",
        Type = "Menu",
        Target = "MainWindow",
        Location = "视图(_V).文档视图(_D)",
        Order = 0
    };

    return new MSFCommand[]
    {
        openCommand,
        editorViewCommand
    };
}

4.2. toolbar

Given the complexity of the toolbar (many types of controls may be added), instead of using commands, the Button is passed to the host program.

internal class CopyButtonWrapper : IWrapper
{
    private PluginContractElement contractElement;

    public CopyButtonWrapper(DocumentViewModel documentViewModel)
    {
        var button = new Button()
        {
            Content = new Image { Width = 16, Height = 16, Source = new BitmapImage(new Uri("pack://application:,,,/EditorPlugin;component/Images/copy.png")) },
            BorderThickness = new System.Windows.Thickness(0),
            BorderBrush = Brushes.Transparent,
            Command = documentViewModel.CopyCommand
        };
        contractElement = new PluginContractElement()
        {
            Id = Guid.NewGuid().ToString(),
            Name = "复制",
            Type = "ToolBar",
            Order = 2,
            Location = "MainWindow.ToolBar.Copy",
            Description = "复制",
            UIContract = new NativeHandleContractInsulator(button)
        };
    }

    public PluginContractElement PluginContractElement => contractElement;
}

4.3. document view

Documentation passes a UserControl to the host program.

internal class DocumentViewWrapper : IWrapper
{
    private PluginContractElement documentContractElement;

    public DocumentViewWrapper(DocumentView documentView)
    {
        documentContractElement = new PluginContractElement()
        {
            Id = Guid.NewGuid().ToString(),
            Name = "文档",
            Type="Document",
            Location = "MainWindow.Document",
            Description = "这是文档",
            UIContract = new NativeHandleContractInsulator(documentView)
        };
    }

    public PluginContractElement PluginContractElement => documentContractElement;
}

4.4. dependency injection

实际项目中我们大多会使用Prism这种提供了依赖注入功能的框架,所以在设计时充分考虑了兼容性,不管是在宿主中还是在插件中都可以使用 Prism 这种框架。

public class EditorPlugin : PluginBase
{
    private readonly DryIoc.Container container;
    private readonly PluginContractElement[] _elements;
    private readonly IMSFCommand[] _commands;
    public EditorPlugin()
    {
        container = new DryIoc.Container();

        RegisterTypes();
        RegisterInstances();

        _commands = CreateCommands();
        _elements = CreateUIElement();
    }

    private void RegisterTypes()
    {
        container.Register<DocumentViewModel>();
        container.Register<DocumentView>();
        container.Register<PluginContractElementBuilder>();
        container.Register<DocumentViewWrapper>();
        container.Register<CopyButtonWrapper>();
        container.Register<CutButtonWrapper>();
        container.Register<PasteButtonWrapper>();
        container.Register<SaveButtonWrapper>();
    }

    ...........
}

5. concluding remarks

This plug-in system allows us to use advanced functions such as sandbox operation, exception isolation, and process communication at a lower cost. Through these advanced functions, we can solve some stubborn problems in the software development process (such as memory usage, multi-core utilization, and problems such as software crashes caused by unknown problems). At the same time, it also gives us unlimited imagination and allows us to build more powerful software based on this.

Keep Exploring

延伸阅读

更多文章
同分类 / 同标签 9/13/2025

Migration from WPF to Avalonia series: Why I have to migrate WPF programs to Avalonia

In the past few years, our host computer software has been mainly developed using WPF and WinForm. These technologies are really easy to use on the Windows platform, and they have also accompanied us through the stage of small-scale trial production to today's large-scale delivery. However, with the development of business and changes in customer needs, the single Windows technology stack has gradually become a hurdle that we must overcome.

继续阅读
同分类 / 同标签 1/26/2025

WPF internationalizes with custom XML files

This article describes in detail the methods of using custom XML files to achieve internationalization in WPF programs, including installing the necessary NuGet package, dynamically obtaining language lists, dynamically switching languages, using translation strings in code and xaml interfaces, etc. It also provides source code links to help developers easily internationalize WPF applications.

继续阅读