在C#中使用AppDomain实现【插件式】开发

前言:

 近期项目中需要实现“热插拔”式的插件程序,例如:定义一个插件接口;由不同开发人员实现具体的插件功能类库;并最终在应用中调用具体插件功能。

 此时需要考虑:插件执行的安全性(隔离运行)和插件可卸载升级。说到隔离运行和可卸载首先想到的是AppDomain。

 那么AppDomain是什么呢?

一、AppDomain介绍

 AppDomain是.Net平台里一个很重要的特性,在.Net以前,每个程序是"封装"在不同的进程中的,这样导致的结果就造就占用资源大,可复用性低等缺点.而AppDomain在同一个进程内划分出多个"域",一个进程可以运行多个应用,提高了资源的复用性,数据通信等. 详见

 CLR在启动的时候会创建系统域(System Domain),共享域(Shared Domain)和默认域(Default Domain),系统域与共享域对于用户是不可见的,默认域也可以说是当前域,它承载了当前应用程序的各类信息(堆栈),所以,我们的一切操作都是在这个默认域上进行."插件式"开发很大程度上就是依靠AppDomain来进行.

 应用程序域具有以下特点:

  • 必须先将程序集加载到应用程序域中,然后才能执行该程序集。

  • 一个应用程序域中的错误不会影响在另一个应用程序域中运行的其他代码。

  • 能够在不停止整个进程的情况下停止单个应用程序并卸载代码。不能卸载单独的程序集或类型,只能卸载整个应用程序域。

二、基于AppDomain实现“热拔式插件”

 通过AppDomain来实现程序集的卸载,这个思路是非常清晰的。由于在程序设计中,非特殊的需要,我们都是运行在同一个应用程序域中。

 由于程序集的卸载存在上述的缺陷,我们必须要关闭应用程序域,方可卸载已经装载的程序集。然而主程序域是不能关闭的,因此唯一的办法就是在主程序域中建立一个子程序域,通过它来专门实现程序集的装载。一旦要卸载这些程序集,就只需要卸载该子程序域就可以了,它并不影响主程序域的执行。 

 实现方式如下图:

  

1、AssemblyDynamicLoader类提供创建子程序域和卸载程序域的方法;
2、RemoteLoader类提供装载程序集、执行接口方法;
3、AssemblyDynamicLoader类获得RemoteLoader类的代理对象,并调用RemoteLoader类的方法;
4、RemoteLoader类的方法在子程序域中完成;

 那么AssemblyDynamicLoader 和 RemoteLoader 如何实现呢?

 1、首先定义RemoteLoader用于加载插件程序集,并提供插件接口执行方法  

复制代码
public class RemoteLoader : MarshalByRefObject
{
    private Assembly _assembly;

    public void LoadAssembly(string assemblyFile)
    {
        _assembly = Assembly.LoadFrom(assemblyFile);         
    }
    public T GetInstance<T>(string typeName) where T : class
    {
        if (_assembly == null) return null;
        var type = _assembly.GetType(typeName);
        if (type == null) return null;
        return Activator.CreateInstance(type) as T;
    }
    public object ExecuteMothod(string typeName, string args)
    {
        if (_assembly == null) return null;
        var type = _assembly.GetType(typeName);
        var obj = Activator.CreateInstance(type);
        if (obj is IPlugin)
        {
            return (obj as IPlugin).Exec(args);
        }
        return null;
    }
}
复制代码

  由于每个AppDomain都有自己的堆栈,内存块,也就是说它们之间的数据并非共享了.若想共享数据,则涉及到应用程序域之间的通信.C#提供了MarshalByRefObject类进行跨域通信,则必须提供自己的跨域访问器.

 2、AssemblyDynamicLoader 主要用于管理应用程序域创建和卸载;并创建RemoteLoader对象

复制代码
using System;
using System.IO;
using System.Reflection;
namespace PluginRunner
{
    public class AssemblyDynamicLoader
    {
        private AppDomain appDomain;
        private RemoteLoader remoteLoader;
        public AssemblyDynamicLoader(string pluginName)
        {
            AppDomainSetup setup = new AppDomainSetup();
            setup.ApplicationName = "app_" + pluginName;
            setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
            setup.PrivateBinPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
            setup.CachePath = setup.ApplicationBase;
            setup.ShadowCopyFiles = "true";
            setup.ShadowCopyDirectories = setup.ApplicationBase;
            AppDomain.CurrentDomain.SetShadowCopyFiles();
            this.appDomain = AppDomain.CreateDomain("app_" + pluginName, null, setup);

            String name = Assembly.GetExecutingAssembly().GetName().FullName;
            this.remoteLoader = (RemoteLoader)this.appDomain.CreateInstanceAndUnwrap(name, typeof(RemoteLoader).FullName);
        }
        /// <summary>
        /// 加载程序集
        /// </summary>
        /// <param name="assemblyFile"></param>
        public void LoadAssembly(string assemblyFile)
        {
            remoteLoader.LoadAssembly(assemblyFile);
        }
        /// <summary>
        /// 创建对象实例
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="typeName"></param>
        /// <returns></returns>
        public T GetInstance<T>(string typeName) where T : class
        {
            if (remoteLoader == null) return null;
            return remoteLoader.GetInstance<T>(typeName);
        }
        /// <summary>
        /// 执行类型方法
        /// </summary>
        /// <param name="typeName"></param>
        /// <param name="methodName"></param>
        /// <returns></returns>
        public object ExecuteMothod(string typeName, string methodName)
        {
            return remoteLoader.ExecuteMothod(typeName, methodName);
        }
        /// <summary>
        /// 卸载应用程序域
        /// </summary>
        public void Unload()
        {
            try
            {
                if (appDomain == null) return;
                AppDomain.Unload(this.appDomain);
                this.appDomain = null;
                this.remoteLoader = null;
            }
            catch (CannotUnloadAppDomainException ex)
            {
                throw ex;
            }
        }
        public Assembly[] GetAssemblies()
        {
            return this.appDomain.GetAssemblies();
        }
    }
}
复制代码

 3、插件接口和实现:

  插件接口:

复制代码
public interface IPlugin
{

    /// <summary>
    /// 执行插件方法
    /// </summary>
    /// <param name="pars">参数json</param>
    /// <returns>执行结果json串</returns>
    object Exec(string pars);

    /// <summary>
    /// 插件初始化
    /// </summary>
    /// <returns></returns>
    bool Init();

}
复制代码

  测试插件实现:

复制代码
public class PrintPlugin : IPlugin
{
    public object Exec(string pars)
    {
        //v1.0
        //return $"打印插件执行-{pars} 完成";
        //v1.1
        return $"打印插件执行-{pars} 完成-更新版本v1.1";
    }

    public bool Init()
    {
        return true;
    }
}
复制代码

  4、插件执行:

复制代码
string pluginName = txtPluginName.Text;
if (!string.IsNullOrEmpty(pluginName) && PluginsList.ContainsKey(pluginName))
{
    var loader = PluginsList[pluginName];
    var strResult = loader.ExecuteMothod("PrintPlugin.PrintPlugin", "Exec")?.ToString();
    MessageBox.Show(strResult);
}
else
{
    MessageBox.Show("插件未指定或未加载");
}
复制代码

 5、测试界面实现:

  创建个测试窗体如下:

三、运行效果

  插件测试基本完成:那么看下运行效果:可以看出当前主程序域中未加载PrintPlugin.dll,而是在子程序集中加载

   

  当我们更新PrintPlugin.dll逻辑,并更新测试程序加载位置中dll,不会出现不允许覆盖提示;然后先卸载dll在再次加载刚刚dll(模拟插件升级) 

   

 到此已实现插件化的基本实现

四、其他

 当然隔离运行和“插件化”都还有其他实现方式,等着解锁。但是只要搞清楚本质、实现原理、底层逻辑这些都相对简单。所以对越基础的内容越要理解清楚。

参考:

 官网介绍:https://docs.microsoft.com/zh-cn/dotnet/framework/app-domains/application-domains

 示例源码:https://github.com/cwsheng/PluginAppDemo

 

 

出处:https://www.cnblogs.com/cwsheng/p/14616685.html

 

=======================================================================================

在winform下实现模块化插件编程-优化版

上一篇《分享在winform下实现模块化插件编程》已经实现了模块化编程,但我认为不够完美,存在以下几个问题:

1.IAppContext中的CreatePlugInForm方法只能依据完整的窗体类型名称formTypeName来动态创建窗体对象,调用不够方便,且该方法创建的窗体不受各模块注册窗体类型AppFormTypes限制,也就是可以创建任何FORM,存在不确定性;

2.动态创建的窗体对象无法直接对其公共属性或公共方法进行调用

3.主应用程序中的LoadComponents方法是通过指定文件夹对所有的DLL文件全部进行获取然后再进行TYPE解析最终才找到实现了ICompoentConfig的类,这个过程比较繁锁效率低下;

4.编译后的应用程序根目录混乱,许多的DLL都与主应用程序EXE在一起;

 下面就针对上述问题进行一一解决。

1.为IAppContext增加几个CreatePlugInForm的扩展方法,同时AppContext实现这几个方法,代码如下:

IAppContext:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/// <summary>
/// 应用程序上下文对象接口
/// 作用:用于收集应用程序必备的一些公共信息并共享给整个应用程序所有模块使用(含动态加载进来的组件)
/// 作者:Zuowenjun
/// 2016-3-26
/// </summary>
public interface IAppContext
{
    /// <summary>
    /// 应用程序名称
    /// </summary>
    string AppName { get; }
 
    /// <summary>
    /// 应用程序版本
    /// </summary>
    string AppVersion { get; }
 
    /// <summary>
    /// 用户登录信息
    /// </summary>
    object SessionUserInfo { get; }
 
    /// <summary>
    /// 用户登录权限信息
    /// </summary>
    object PermissionInfo { get; }
 
    /// <summary>
    /// 应用程序全局缓存,整个应用程序(含动态加载的组件)均可进行读写访问
    /// </summary>
    ConcurrentDictionary<string, object> AppCache { get; }
 
    /// <summary>
    /// 应用程序主界面窗体,各组件中可以订阅或获取主界面的相关信息
    /// </summary>
    Form AppFormContainer { get; }
 
    /// <summary>
    /// 动态创建在注册列表中的插件窗体实例
    /// </summary>
    /// <param name="formType"></param>
    /// <returns></returns>
    Form CreatePlugInForm(Type formType,params object[] args);
 
    /// <summary>
    /// 动态创建在注册列表中的插件窗体实例
    /// </summary>
    /// <param name="formTypeName"></param>
    /// <returns></returns>
    Form CreatePlugInForm(string formTypeName, params object[] args);
 
    /// <summary>
    /// 动态创建在注册列表中的插件窗体实例
    /// </summary>
    /// <param name="formTypeName"></param>
    /// <returns></returns>
    Form CreatePlugInForm<TForm>(params object[] args) where TForm : Form;
 
}

AppContext:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/// <summary>
/// 应用程序上下文对象类
/// 作者:Zuowenjun
/// 2016-3-26
/// </summary>
public class AppContext : IAppContext
{
 
    internal static AppContext Current;
 
    internal Dictionary<string, Type> AppFormTypes
    {
        get;
        set;
    }
 
    public string AppName
    {
        get;
        private set;
    }
 
    public string AppVersion
    {
        get;
        private set;
    }
 
    public object SessionUserInfo
    {
        get;
        private set;
    }
 
    public object PermissionInfo
    {
        get;
        private set;
    }
 
    public ConcurrentDictionary<string, object> AppCache
    {
        get;
        private set;
    }
 
    public System.Windows.Forms.Form AppFormContainer
    {
        get;
        private set;
    }
 
 
    public AppContext(string appName, string appVersion, object sessionUserInfo, object permissionInfo, Form appFormContainer)
    {
        this.AppName = appName;
        this.AppVersion = appVersion;
        this.SessionUserInfo = sessionUserInfo;
        this.PermissionInfo = permissionInfo;
        this.AppCache = new ConcurrentDictionary<string, object>();
        this.AppFormContainer = appFormContainer;
    }
 
    public System.Windows.Forms.Form CreatePlugInForm(Type formType, params object[] args)
    {
        if (this.AppFormTypes.ContainsValue(formType))
        {
            return Activator.CreateInstance(formType, args) as Form;
        }
        else
        {
            throw new ArgumentOutOfRangeException(string.Format("该窗体类型{0}不在任何一个模块组件窗体类型注册列表中!", formType.FullName), "formType");
        }
    }
 
    public System.Windows.Forms.Form CreatePlugInForm(string formTypeName, params object[] args)
    {
        if (!formTypeName.Contains('.'))
        {
            formTypeName = "." + formTypeName;
        }
        var formTypes = this.AppFormTypes.Where(t => t.Key.EndsWith(formTypeName, StringComparison.OrdinalIgnoreCase)).ToArray();
        if (formTypes == null || formTypes.Length != 1)
        {
            throw new ArgumentException(string.Format("从窗体类型注册列表中未能找到与【{0}】相匹配的唯一窗体类型!", formTypeName), "formTypeName");
        }
        return CreatePlugInForm(formTypes[0].Value, args);
    }
 
 
    public Form CreatePlugInForm<TForm>(params object[] args) where TForm : Form
    {
        return CreatePlugInForm(typeof(TForm), args);
    }
}

从AppContext类中可以看出,CreatePlugInForm方法有三个重载,分别支持依据TYPE、泛型、模糊类型名来动态创建窗体对象,同时若窗体类型含有参的构造函数,那么后面的args参数数组赋值即可。

2.为Form类型增加三个扩展方法,分别是:SetPublicPropertyValue(动态给公共属性赋值)、GetPublicPropertyValue(动态获取公共属性的值)、ExecutePublicMethod(动态执行公共方法(含公共静态方法)),弥补动态创建的窗体无法对公共成员进行操作的问题,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public static class FormExtension
{
    /// <summary>
    /// 动态给公共属性赋值
    /// </summary>
    /// <param name="form"></param>
    /// <param name="propertyName"></param>
    /// <param name="propertyValue"></param>
    public static void SetPublicPropertyValue(this Form form, string propertyName, object propertyValue)
    {
        var formType = form.GetType();
        var property = formType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
        if (property != null)
        {
            property.SetValue(form, propertyValue, null);
        }
        else
        {
            throw new Exception(string.Format("没有找到名称为:{0}的公共属性成员!", propertyName));
        }
    }
 
    /// <summary>
    /// 动态获取公共属性的值
    /// </summary>
    /// <typeparam name="TResult"></typeparam>
    /// <param name="form"></param>
    /// <param name="propertyName"></param>
    /// <param name="defaultPropertyValue"></param>
    /// <returns></returns>
    public static TResult GetPublicPropertyValue<TResult>(this Form form, string propertyName, TResult defaultPropertyValue)
    {
        var formType = form.GetType();
        var property = formType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
        var proValue = property.GetValue(form, null);
        if (property != null)
        {
            try
            {
                return (TResult)Convert.ChangeType(proValue, typeof(TResult));
            }
            catch
            {
                return defaultPropertyValue;
            }
        }
        else
        {
            throw new Exception(string.Format("没有找到名称为:{0}的公共属性成员!", propertyName));
        }
    }
 
    /// <summary>
    /// 动态执行公共方法(含公共静态方法)
    /// </summary>
    /// <param name="form"></param>
    /// <param name="methodName"></param>
    /// <param name="args"></param>
    /// <returns></returns>
    public static object ExecutePublicMethod(this Form form, string methodName, params object[] args)
    {
        var formType = form.GetType();
        var method = formType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.IgnoreCase);
        if (method != null)
        {
            return method.Invoke(form, args);
        }
        else
        {
            throw new Exception(string.Format("没有找到名称为:{0}且形数个数有:{1}个的公共方法成员!", methodName, args == null ? 0 : args.Count()));
        }
    }
}

使用很简单就不再演示说明了。

3.动态加载符合条件的模块组件,之前的LoadComponents效率太低,而我这里想实现类似ASP.NET 的Handler或Module可以动态的从CONFIG文件中进行增减配置,ASP.NET 的Handler、Module配置节点如下:

若需实现从CONFIG文件配置,那么就需要增加自定义节点配置,如:compoents,当然如果为能省事也可以直接用appSettings节点,要增加自定义节点配置,就需要定义与自定义节点相关的类,具体的实现方式,百度搜索一下就知道了,我这里也直接给出一个参考地址:http://www.cnblogs.com/lichaoliu/archive/2010/11/03/1868245.html,如下是我实现的compoents节点相关的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/// <summary>
/// 组件配置节点类
/// 作者:Zuowenjun
/// 2016-3-30
/// </summary>
public class CompoentConfigurationSection : ConfigurationSection
{
    private static readonly ConfigurationProperty s_property = new ConfigurationProperty(
            string.Empty, typeof(ComponentCollection), null, ConfigurationPropertyOptions.IsDefaultCollection);
 
    [ConfigurationProperty("", Options = ConfigurationPropertyOptions.IsDefaultCollection)]
    public ComponentCollection Components
    {
        get
        {
            return (ComponentCollection)base[s_property];
        }
    }
 
 
    [ConfigurationProperty("basePath", IsRequired = false)]
    public string BasePath
    {
        get
        {
            return ReMapBasePath(this["basePath"].ToString());
        }
        set
        {
            this["basePath"] = ReMapBasePath(value);
        }
    }
 
    private string ReMapBasePath(string basePath)
    {
        if (basePath.Trim().StartsWith("~\\"))
        {
            basePath = basePath.Replace("~\\", AppDomain.CurrentDomain.BaseDirectory + "\\");
        }
        return basePath;
    }
}
 
 
 
/// <summary>
/// 组件配置集合类
/// 作者:Zuowenjun
/// 2016-3-30
/// </summary>
[ConfigurationCollection(typeof(ComponentElement))]
public class ComponentCollection : ConfigurationElementCollection
{
 
    public ComponentCollection():base(StringComparer.OrdinalIgnoreCase)
    {
 
    }
 
    protected override ConfigurationElement CreateNewElement()
    {
        return new ComponentElement();
    }
 
    protected override object GetElementKey(ConfigurationElement element)
    {
        return (element as ComponentElement).FileName;
    }
 
    new public ComponentElement this[string fileName]
    {
        get
        {
            return (ComponentElement)base.BaseGet(fileName);
        }
    }
 
    public void Add(ComponentElement item)
    {
        this.BaseAdd(item);
    }
 
    public void Clear()
    {
        base.BaseClear();
    }
 
    public void Remove(string fileName)
    {
        base.BaseRemove(fileName);
    }
 
}
 
 
/// <summary>
/// 组件配置项类
/// 作者:Zuowenjun
/// 2016-3-30
/// </summary>
public class ComponentElement : ConfigurationElement
{
 
    [ConfigurationProperty("fileName", IsRequired = true, IsKey = true)]
    public string FileName
    {
        get { return this["fileName"].ToString(); }
        set { this["fileName"] = value; }
    }
 
    [ConfigurationProperty("entryType", IsRequired = true)]
    public string EntryType
    {
        get { return this["entryType"].ToString(); }
        set { this["entryType"] = value; }
    }
 
    [ConfigurationProperty("sortNo", IsRequired = false, DefaultValue = 0)]
    public int SortNo
    {
        get { return Convert.ToInt32(this["sortNo"]); }
        set { this["sortNo"] = value; }
    }
}

最终实现的配置示例如下:

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="compoents" type="WMS.PlugIn.Framework.Configuration.CompoentConfigurationSection,WMS.PlugIn.Framework"/>
  </configSections>
  <compoents basePath="~\Libs\">
    <add fileName="WMS.Com.CW.dll" entryType="WMS.Com.CW.CompoentConfig" sortNo="1" />
  </compoents>

然后主应用程序这边改进LoadComponents方法,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private void LoadComponents()
{
    var compoents = ConfigurationManager.GetSection("compoents") as CompoentConfigurationSection;
    if (compoents == null) return;
 
    string basePath = compoents.BasePath;
    if (string.IsNullOrWhiteSpace(basePath))
    {
        basePath = Program.AppLibsDir;
    }
 
    Type targetFormType = typeof(Form);
 
    foreach (ComponentElement item in compoents.Components)
    {
        string filePath = Path.Combine(basePath, item.FileName);
        var asy = Assembly.LoadFrom(filePath);
        var type = asy.GetType(item.EntryType, true);
        ICompoent compoent = null;
        var config = (ICompoentConfig)Activator.CreateInstance(type);
        config.CompoentRegister(AppContext.Current, out compoent);//关键点在这里,得到组件实例化后的compoent
        if (compoent != null)
        {
            foreach (Type formType in compoent.FormTypes)//将符合的窗体类型集合加到AppContext的AppFormTypes中
            {
                if (targetFormType.IsAssignableFrom(formType) && !formType.IsAbstract)
                {
                    AppContext.Current.AppFormTypes.Add(formType.FullName, formType);
                }
            }
        }
    }
}

对比改进前后的LoadComponents方法,有没有觉得改进后的代码效率更高一些了,我认为效率高在避免了文件夹的扫描及类型的查询,改进后的方法都是通过配置文件的信息直接获取程序集及指定的类型信息。

4.改进了插件编程这块后,最后一个要解决的其实与插件编程无关,但因为我在项目中也同时进行了改进,所以也在此一并说明实现思路。

要想将引用的DLL放到指定的文件夹下,如:Libs,就需要了解程序集的寻找原理,具体了解请参见:C#开发奇技淫巧三:把dll放在不同的目录让你的程序更整洁,说白了只要设置或改变其私有目录privatePath,就能改变程序集加载时寻找的路径,网上大部份是采用如下配置的方式来修改privatePath,如下:

1
2
3
4
5
<runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <probing privatePath="Libs"/>
     </assemblyBinding>
</runtime>

而我这里采用另一种方法:通过订阅AssemblyResolve事件(该事件是加载程序失败时触发)然后在订阅的事件中动态加载缺失的程序集来实现的,好处是安全,不用担心路径被改造成程序无法正常运行的情况,实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
static class Program
{
    public static string AppLibsDir = null;
 
    /// <summary>
    /// 应用程序的主入口点。
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {
        AppLibsDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Libs\");
        AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
 
        AddEnvironmentPaths(AppLibsDir);
    }
 
    static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        Assembly assembly = null, objExecutingAssemblies = null;
        objExecutingAssemblies = Assembly.GetExecutingAssembly();
        AssemblyName[] arrReferencedAssmbNames = objExecutingAssemblies.GetReferencedAssemblies();
 
        foreach (AssemblyName assmblyName in arrReferencedAssmbNames)
        {
            if (assmblyName.FullName.Substring(0, assmblyName.FullName.IndexOf(",")) == args.Name.Substring(0, args.Name.IndexOf(",")))
            {
                string path = System.IO.Path.Combine(AppLibsDir, args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll");
                assembly = Assembly.LoadFrom(path);
                break;
            }
        }
        return assembly;
    }
 
    static void AddEnvironmentPaths(params string[] paths)
    {
        var path = new[] { Environment.GetEnvironmentVariable("PATH") ?? string.Empty };
 
        string newPath = string.Join(Path.PathSeparator.ToString(), path.Concat(paths));
 
        Environment.SetEnvironmentVariable("PATH", newPath);
    }
}

里面包括一个动态增加环境路径的方法:AddEnvironmentPaths,其作用网上也讲过了,就是处理通过[DllImport]中的程序集的加载。

这样就完成了将引用的DLL放到指定的目录中:libs,当然在主应用程序引用DLL时,请将复制到本地设为False,这样编译后的程序根目录才会干净如你所愿。

以上就是本文的全部内容,代码也都已贴出来了,大家可以直接COPY下来用,当然其实模块化插件编程还有其它的细节,比如:各模块组件的更新,各模块组件的安全性问题等,这些大家有兴趣也可以研究一下,本文若有不足,欢迎指出,谢谢!

  

 

出处:https://www.cnblogs.com/zuowj/p/5383672.html

posted on 2021-04-12 09:09  jack_Meng  阅读(4784)  评论(2编辑  收藏  举报

导航