【问题标题】:Loading assemblies as plugins将程序集作为插件加载
【发布时间】:2010-11-18 17:57:56
【问题描述】:

我有一个 winforms 项目,它允许未来的开发人员为应用程序创建插件。 Winforms 应用程序包含开发人员在创建插件时需要引用的所有接口和类。因此,当我创建插件时,我引用了主应用程序的 dll。

当主应用程序初始化时,我创建一个单独的域来加载所有插件 dll。我这样做的原因是我可以使用 appDomain.unload 调用随意删除插件,然后重新加载剩余的插件。 当我在 VS2008 中启动调试时,我的应用程序初始化时,它会加载第一个插件,但我收到警告说我需要加载插件引用的 dll,我用它来引用主应用程序的接口。

现在我的问题是在将 dll 加载到子域之前,我可以使用主应用程序的接口来创建它们的实例以供插件用作参考吗?如果是,我该怎么做?任何帮助表示赞赏。

这是我的应用程序的插件管理器,它加载在 app.config 文件中找到的插件。我只保留了我需要帮助的方法和类的构造函数。 在本课程中,我正在阅读 app.cinfig 文件并将 customConfigSection 的内容放入 customConfig 集合中。当调用 loadAssemblies 时,我循环遍历集合并将程序集添加到构造函数中创建的子域。

using System;
using MyApp.Data;
using MyApp.Interfaces;
using MyApp.Variables;
using System.Reflection;

namespace MyApp.Core
{
    /// <summary>
    /// This object helps the application manage its scalable and extended components
    /// called plugins.
    /// </summary>
    public class PlugInManager
    {
        public PlugInManager()
        {

            //appDomain setup
            pluginDomainSetup = new AppDomainSetup();
            pluginDomainSetup.ApplicationBase = pluginDomainLocation;
            pluginDomainSetup.DisallowCodeDownload = true;
            string pluginApplicationName = string.Format(MAOIE.Variables.Constants.PLUGIN_APPLICATION_NAME);

            //appDomain creation
            pluginDomain = AppDomain.CreateDomain(pluginApplicationName, null, pluginDomainSetup);

            //Loads the values located in the config file
            LoadPluginConfiguration();
            //Load any existing plugins in the directories
            LoadAssemblies();
        }

        private void LoadAssemblies()
        {
            //I"m thinking I should add this the referenced libraries to the subdomain here.

            //AppDomain.Unload(this.pluginDomain);   
            string reference = GetReferencePath();
            reference += Variables.Constants.MAOIE_CORE_DLL;


            //Iterate through the items found in the app.config file.
            foreach (PluginSetting item in this.PluginConfigSettings.PluginItems)
            {
                string file = GetPluginPath();
                file += item.PluginFileName;

                switch (item.PluginType)
                {
                case Constants.PluginType.pluginTypeA:
                    pluginDomain.CreateInstanceFrom(file, item.PluginAssemblyType);

                    IPluginTypeA ia = (IPluginTypeA)Activator.CreateInstance(pluginDomain, item.PluginFileName, item.PluginAssemblyType);
                    Plugable<IPluginTypeA> pia = new Plugable<IPluginTypeA>();
                    pia.ConcreteClass = ia;
                    pia.Core = false;
                    //collection used throughout the application
                    this.aerodynamicAnalyzers.Add(pia);

                    return;
                case Constants.PluginType.pluginTypeB:
                    pluginDomain.CreateInstanceFrom(file, item.PluginAssemblyType);

                    IPluginTypeB ib = (IPluginTypeB)Activator.CreateInstance(pluginDomain, item.PluginFileName, item.PluginAssemblyType);
                    Plugable<IPluginTypeB> pib = new Plugable<IPluginTypeB>();
                    piB.ConcreteClass = ib;
                    pim.Core = false;
                    //collection used throughout the application
                    this.missionAnalyzers.Add(pib);
                    return;
                case Constants.PluginType.pluginTypeC:
                    pluginDomain.CreateInstanceFrom(file, item.PluginAssemblyType);

                    IPluginTypeC ic = (IPluginTypeC)Activator.CreateInstance(pluginDomain, item.PluginFileName, item.PluginAssemblyType);
                    Plugable<IPluginTypeC> pic = new Plugable<IPluginTypeC>();
                    pic.ConcreteClass = ic;
                    pic.Core = false;
                    //collection used throughout the application
                    this.pluginTypeCs.Add(pio);
                    return;
                case Constants.PluginType.pluginTypeD:
                    pluginDomain.CreateInstanceFrom(file, item.PluginAssemblyType);

                    IPluginTypeD id = (IPluginTypeD)Activator.CreateInstance(pluginDomain, item.PluginFileName, item.PluginAssemblyType);
                    Plugable<IPluginTypeD> piw = new Plugable<IPluginTypeD>();
                    pid.ConcreteClass = id;
                    pid.Core = false;
                    //collection used throughout the application
                    this.pluginTypeDs.Add(pid);
                    return;
                }
            }
        }
    }
    //end PlugInManager

}
//end namespace  MyApp.Core

下一个类是单独项目中的一个类。我正在存根一个空插件来测试我的 pluginManager 的 LoadAssemblies 方法。我添加了对 MyApp.Core.dll 的引用,该引用由 Visual Studio 复制到该项目的 bin 目录中。我需要这个来实现在主应用程序中找到的接口。

这个类只是 getter 和 setter 加上一个空方法。

///////////////////////////////////////////////////////////
//  testPluginA.cs
// used as an empty class to test the import of plugins into the main application.
///////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Text;
using MyApp.Core;

namespace testPluginA
{
    public class testPluginA : MyApp.Interfaces.IPluginTypeA
    {
        public testPluginA()
        { 

        }

        private string name = "testPluginA";
        private string desc = "Test Plugin A 1";
        /// <summary>
        /// The description of the plugin.
        /// </summary>
        public string Description { get{return this.desc;} }
        /// <summary>
        /// The display name of the plugin.
        /// </summary>
        public string FriendlyName { get{return this.name;} }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="mp"></param>
        public void Optimize(MyApp.Data.Car car)
        { 
            //does nothing
        }

【问题讨论】:

  • 不确定您的问题到底是什么。在 Visual Studio 中调试时是否有问题?还是实际编译应用程序有问题?还是在运行它?
  • 问题在于运行应用程序。当我的第一个插件加载时,我收到一个运行时错误,指出我的插件没有加载 MyApp.Core。失败的代码行是加载我的程序集,它使用 MyApp.Core 作为参考。该引用是在我构建的模拟插件项目中进行的。

标签: c# .net winforms assemblies


【解决方案1】:

将插件加载到同一个域中,但让它们实现一个公开Load 和Unload 方法的接口。 Unload 方法的公共合约 [至少] 如下:

  • Dispose()(如果适用)和 null 对插件使用的资源的所有引用,以便 GC [在将来的某个时间点] 收集它们
  • 将其“连接”(无论其形式如何)移除到主应用程序中,以便主应用程序的状态与加载插件之前的状态相同
  • 将插件置于Load 是有效操作的状态

您的插件作者有责任遵守本合同。无论如何,您正在加载他们的代码以执行 - 如果他们违反规则,无论您是否将它们放在自己的 AppDomain 中, 都会出错。

编辑:就我个人而言,我认为Managed Extensibility Framework至少值得在您构建 API 时进行检查(如果它满足您的需要则使用),尽管这里有些人不同意。

【讨论】:

  • 我没有把它放在同一个域中的原因是如果用户想删除文件插件,则释放并删除 dll。我能做到这一点的唯一方法是使用 appDomain.Unload。否则 dll 仍将被当前会话锁定,直到用户退出应用程序。你的推荐能满足这个要求吗?
  • 不,但是您可以将程序集复制到某种临时缓存中并从那里加载它,当程序存在时您可以清除缓存。 IMO,在程序运行时删除插件肯定是证明跨 AppDomain 插件操作性能下降的罕见情况。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-19
  • 1970-01-01
  • 2018-11-18
  • 1970-01-01
相关资源
最近更新 更多