C# 通过 AppDomain 应用程序域实现程序集动态卸载或加载
AppDomain 表示应用程序域,它是一个应用程序在其中执行的独立环境。每个应用程序只有一个主应用程序域,但是一个应用程序可以创建多个子应用程序域。
因此可以通过 AppDomain 创建新的应用程序域,在新创建的子应用程序域中加载执行程序集并且在执行完毕后释放程序集资源,来实现系统在运行状态下,程序集的动态加载或卸载,从而达到系统运行中程序集热更新的目的。
所谓应用程序域,.Net引入的一个概念,指的是一种边界,它标识了代码的运行范围,在其中产生的任何行为,包括异常都不会影响到其他应用程序域,起到安全隔离的效果。也可以看成是一个轻量级的进程。
一个进程可以包含多个应用程序域,各个域之间相互独立。如下是一个.net进程的组成(图片来自网络)

以下为整个原理的实现代码
主应用程序入口:
using Kernel.ServiceAgent;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting;
using System.Text;
using System.Threading;
using System.Threading.Tasks; namespace Kernel.App
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""); using (ServiceManager<IObjcet> manager = new ServiceManager<IObjcet>())
{
string result = manager.Proxy.Put("apprun one");
Console.WriteLine(result);
Console.WriteLine("");
Console.WriteLine(" Thread AppDomain info ");
Console.WriteLine(manager.CotrProxy.FriendlyName);
Console.WriteLine(Thread.GetDomain().FriendlyName);
Console.WriteLine(manager.CotrProxy.BaseDirectory);
Console.WriteLine(manager.CotrProxy.ShadowCopyFiles);
Console.WriteLine("");
} Console.ReadLine();
}
}
}
创建新的应用程序域并且在新的应用程序域中调用透明代理类:
using Kernel.Interface;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Policy;
using System.Text;
using System.Threading;
using System.Threading.Tasks; namespace Kernel.ServiceAgent
{
public class ServiceManager<T> : IDisposable where T : class
{
private AppDomain ctorProxy = null; /// <summary>
/// 应用程序运行域容器
/// </summary>
public AppDomain CotrProxy
{
get { return ctorProxy; }
} private T proxy = default(T); public T Proxy
{
get
{
if (proxy == null)
{
proxy = (T)InitProxy(AssemblyPlugs);
}
return proxy;
}
} private string assemblyPlugs; /// <summary>
/// 外挂插件程序集目录路径
/// </summary>
public string AssemblyPlugs
{
get {
assemblyPlugs = ConfigHelper.GetVaule("PrivatePath");
if (assemblyPlugs.Equals("")){
assemblyPlugs = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
}
if (!Directory.Exists(assemblyPlugs))
{
Directory.CreateDirectory(assemblyPlugs);
}
return assemblyPlugs;
}
set { assemblyPlugs = value; }
} public ServiceManager()
{
if (proxy == null)
{
proxy = (T)InitProxy(AssemblyPlugs);
}
} private T InitProxy(string assemblyPlugs)
{
try
{
//AppDomain.CurrentDomain.SetShadowCopyFiles();
//Get and display the friendly name of the default AppDomain.
//string callingDomainName = Thread.GetDomain().FriendlyName; //Get and display the full name of the EXE assembly.
//string exeAssembly = Assembly.GetEntryAssembly().FullName;
//Console.WriteLine(exeAssembly); AppDomainSetup ads = new AppDomainSetup();
ads.ApplicationName = "Shadow"; //应用程序根目录
ads.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory; //子目录(相对形式)在AppDomainSetup中加入外部程序集的所在目录,多个目录用分号间隔
ads.PrivateBinPath = assemblyPlugs;
//设置缓存目录
ads.CachePath = ads.ApplicationBase;
//获取或设置指示影像复制是打开还是关闭
ads.ShadowCopyFiles = "true";
//获取或设置目录的名称,这些目录包含要影像复制的程序集
ads.ShadowCopyDirectories = ads.ApplicationBase; ads.DisallowBindingRedirects = false;
ads.DisallowCodeDownload = true; ads.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; //Create evidence for the new application domain from evidence of
Evidence adevidence = AppDomain.CurrentDomain.Evidence; // Create the second AppDomain.
ctorProxy = AppDomain.CreateDomain("AD #2", adevidence, ads); //Type.GetType("Kernel.TypeLibrary.MarshalByRefType").Assembly.FullName
string assemblyName = Assembly.GetExecutingAssembly().GetName().FullName;
//string assemblyName = typeof(MarshalByRefType).Assembly.FullName // Create an instance of MarshalByRefObject in the second AppDomain.
// A proxy to the object is returned. Console.WriteLine("CtorProxy:" + Thread.GetDomain().FriendlyName); //TransparentFactory factory = (IObjcet)ctorProxy.CreateInstance("Kernel.TypeLibrary",
"Kernel.TypeLibrary.TransparentFactory").Unwrap();
TransparentAgent factory = (TransparentAgent)ctorProxy.CreateInstanceAndUnwrap(assemblyName,
typeof(TransparentAgent).FullName); Type meetType = typeof(T);
string typeName = AssemblyHelper.CategoryInfo(meetType); object[] args = new object[];
string assemblyPath = ctorProxy.SetupInformation.PrivateBinPath; //IObjcet ilive = factory.Create(@"E:\Plug\Kernel.SimpleLibrary.dll", "Kernel.SimpleLibrary.PlugPut", args);
T obj = factory.Create<T>(assemblyPath, typeName, args); return obj;
}
catch (System.Exception)
{
throw;
}
} /// <summary>
/// 卸载应用程序域
/// </summary>
public void Unload()
{
try
{
if (ctorProxy != null)
{
AppDomain.Unload(ctorProxy);
ctorProxy = null;
}
}
catch(Exception)
{
throw;
}
} public void Dispose()
{
this.Unload();
}
}
}
创建应用程序代理类:
using Kernel.Interface;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks; namespace Kernel.ServiceAgent
{
public class TransparentAgent : MarshalByRefObject
{
private const BindingFlags bfi = BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance; public TransparentAgent() { } /// <summary> Factory method to create an instance of the type whose name is specified,
/// using the named assembly file and the constructor that best matches the specified parameters. </summary>
/// <param name="assemblyFile"> The name of a file that contains an assembly where the type named typeName is sought. </param>
/// <param name="typeName"> The name of the preferred type. </param>
/// <param name="constructArgs"> An array of arguments that match in number, order,
/// and type the parameters of the constructor to invoke, or null for default constructor. </param>
/// <returns> The return value is the created object represented as IObjcet. </returns>
public IObjcet Create(string assemblyFile, string typeName, object[] args)
{
return (IObjcet)Activator.CreateInstanceFrom(assemblyFile, typeName, false, bfi, null, args, null, null).Unwrap();
} public T Create<T>(string assemblyPath, string typeName, object[] args)
{
string assemblyFile = AssemblyHelper.LoadAssemblyFile(assemblyPath, typeName);
return (T)Activator.CreateInstanceFrom(assemblyFile, typeName, false, bfi, null, args, null, null).Unwrap();
}
}
}
所有涉及到需要动态加载或释放的资源,都需要放在代理类中进行操作,只有在此代理类中进行托管的代码才是属于新建的应用程序域的操作。
using Kernel.Interface;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks; namespace Kernel.ServiceAgent
{
public class AssemblyHelper
{
/// <summary>
/// 获取泛型类中指定属性值
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static string CategoryInfo(Type meetType)
{
object[] attrList = meetType.GetCustomAttributes(typeof(CategoryInfoAttribute), false);
if (attrList != null)
{
CategoryInfoAttribute categoryInfo = (CategoryInfoAttribute)attrList[];
return categoryInfo.Category;
}
return "";
} public static string LoadAssemblyFile(string assemblyPlugs, string typeName)
{
string path = string.Empty;
DirectoryInfo d = new DirectoryInfo(assemblyPlugs);
foreach (FileInfo file in d.GetFiles("*.dll"))
{
Assembly assembly = Assembly.LoadFile(file.FullName);
Type type = assembly.GetType(typeName, false);
if (type != null)
{
path = file.FullName;
}
}
return path;
}
}
}
读取配置文件信息:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Configuration; namespace Kernel.ServiceAgent
{
public class ConfigHelper
{
public static string GetVaule(string configName)
{
string configVaule = ConfigurationManager.AppSettings[configName];
if (configVaule != null && configVaule != "")
{
return configVaule.ToString();
}
return "";
}
}
}
配置文件相关配置信息:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<appSettings>
<add key="PrivatePath" value="E:\Plugins"/>
</appSettings>
</configuration>
创建接口信息:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Kernel.Interface
{
[CategoryInfo("Kernel.SimpleLibrary.PlugPut", "")]
public interface IObjcet
{
void Put(); string Put(string plus);
}
}
创建接口自定义属性:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Kernel.Interface
{
/// <summary>
/// 设置接口实现类自定义标注属性
/// </summary>
///
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)]
public class CategoryInfoAttribute : Attribute
{
public string Category { get; set; } public string Describtion { get; set; } /// <summary>
/// 设置实现类自定义标注属性
/// </summary>
/// <param name="category"></param>
/// <param name="describtion"></param>
public CategoryInfoAttribute(string category, string describtion)
{
this.Category = category;
this.Describtion = describtion;
}
}
}
创建继承至IObjcet接口带有具体操作的实现类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Kernel.Interface; namespace Kernel.SimpleLibrary
{
[Serializable]
public class PlugPut : MarshalByRefObject, IObjcet
{ private string plugName = "my plugName value is default!"; public string PlugName
{
get { return plugName; }
set { plugName = value; }
} public PlugPut() { } public PlugPut(string plusName)
{
this.PlugName = plusName;
} public void Put()
{
Console.WriteLine("Default plug value is:" + plugName);
} public string Put(string plus)
{
Console.WriteLine("Put plus value is:" + plus);
return ("-------------------- PlugPut result info is welcome -------------------------");
}
}
}
继承至IObjcet接口带有具体操作的实现类,就是属于需要动态替换更新的程序集,所以最好将其编译在一个单独的程序集中,插件目录在配置文件中可配置,示例中放置在E:\Plugins 目录下,示例中代码最后将生成 Kernel.SimpleLibrary.DLL ,最后将编译好的程序集放置在 E:\Plugins\Kernel.SimpleLibrary.DLL 路径下,程序运行后将加载此程序集,加载完毕运行完毕后并释放此程序集。
以下两句较为重要,最好设置一下,不设置的后果暂时没有尝试
//获取或设置指示影像复制是打开还是关闭
ads.ShadowCopyFiles = "true";
//获取或设置目录的名称,这些目录包含要影像复制的程序集
ads.ShadowCopyDirectories = ads.ApplicationBase;
当程序运行起来后,程序集加载之后会在设置的应用程序域缓存目录中复制一份程序集的副本,然后运行副本中的程序集,释放掉本身加载的程序集。以上示例中会在主程序目录下生成一个Shadow 目录,此目录下包含了程序集的副本文件。
小节:
如果在另一个AppDomain 中加载程序集,然后获取Type,最后在主AppDomain中使用CreateInstance中的Type重载创建对象,结果会是Type所属的程序集会被加入到当前AppDomain中,然后Type的实例会在当前AppDomain中创建。
只有继承至 MarshalByRefObject 的透明代理类才能够进行跨域操作。
所以需要在继承至 MarshalByRefObject 的透明代理类中进行相关操作然后返回给主应用程序域,只有在代理类中进行的代码操作才是属于新建的应用程序域。否则任何运行代理类以外的代码都是属于主应用程序域。
此章节只是讲解了程序集动态加载或卸载热插拔的实现方式,有关AppDomain 和 AppDomainSetup 具体信息可以参考MSDN上面的文档。
C# 通过 AppDomain 应用程序域实现程序集动态卸载或加载的更多相关文章
- netload 加载程序集抛异常----无法加载程序集解决办法
netload 加载程序集抛异常----无法加载程序集 错误信息如下: 无法加载程序集.错误详细信息: System.BadImageFormatException: 未能加载文件或程序集“file: ...
- .Net Core 程序集管理说明(加载)
.NET CORE 的程序集加载管理和以前的 .NET 发生了很大的变化, 在 .NET CORE 里, 程序集的加载, 依赖了 xx.deps.json 文件, deps.json 文件里,定义了程 ...
- c++ 动态库的加载
转载:https://blog.csdn.net/ztq_12345/article/details/99677769 使用ide是vs, 使用Windows.h下的3个函数对动态库进行加载第一个:H ...
- PHP+Mysql+easyui点击左侧tree菜单对应表名右侧动态生成datagrid加载表单数据(二)
关于tree菜单生成,参考我的另一篇博文地址tree 菜单 实现功能:点击左侧tree菜单中的table,右侧通过datagrid加载出该表对用的所有数据 难点:获取该表的所有列名,动态生成datag ...
- [f]动态判断js加载完成
在正常的加载过程中,js文件的加载是同步的,也就是说在js加载的过程中,浏览器会阻塞接下来的内容的解析.这时候,动态加载便显得尤为重要了,由于它是异步加载,因此,它可以在后台自动下载,并不会妨碍其它内 ...
- 动态库DLL加载方式-静态加载和动态加载
静态加载: 如果你有a.dll和a.lib,两个文件都有的话可以用静态加载的方式: message函数的声明你应该知道吧,把它的声明和下面的语句写到一个头文件中 #pragma comment(lib ...
- Extjs4.1.x使用Application动态按需加载MVC各模块
我们知道Extjs4之后提出了MVC模块开发,将以前肥厚的js文件拆分成小的js模块[model\view\controller\store\form\data等],通过controller拼接黏合, ...
- [ExtJS5学习笔记]第二十节 Extjs5配合数组的push方法,动态创建并加载组件
本文地址:http://blog.csdn.net/sushengmiyan/article/details/39226773 官方例子:http://docs.sencha.com/extjs/5. ...
- linux驱动动态与静态加载
在Linux中驱动的加载方式有动态加载和静态加载.动态加载,即驱动不添加到内核中,在内核启动完成后,仅在用到这一驱动时才会进行加载静态加载,驱动编译进内核中,随内核的启动而完成驱动的加载.添加字符驱动 ...
随机推荐
- JavaScript中的局部作用域及常量的定义
局部作用域 通常JavaScript的作用域是函数内部,在类似for循环的语句块中是无法申明局部变量的. function exm() { for (var i=0; i<100; i++) { ...
- intellij idea android错误: Missing styles. Is the correct theme chosen for this layout?
Missing styles. Is the correct theme chosen for this layout? Use the Theme combo box above the layou ...
- cnblogs的代码高亮
由于不喜欢cnblogs原来的代码高亮方案,于是自己瞎搞,外加看这位大神的blog以及BZOJ的代码高亮,终于是搞出来了...讲讲怎么弄吧. 当然对于了解css的大神可以无视以下文字…… 其实就是登上 ...
- luogu P1418 选点问题
题目描述 给出n个点,m条边,每个点能控制与其相连的所有的边,要求选出一些点,使得这些点能控制所有的边,并且点数最少.同时,任意一条边不能被两个点控制 输入输出格式 输入格式: 第一行给出两个正整数n ...
- MySQL json 类型操作快速入门
MySQL 5.7.8开始支持 json类型. create table t(id int,js json,PRIMARY KEY (`id`)) 插入数据insert into t values(1 ...
- HDU 3389 Game(博弈)
Game Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submis ...
- Looking deeper into SQL Server using Minidumps
https://blogs.msdn.microsoft.com/sqlcat/2009/09/11/looking-deeper-into-sql-server-using-minidumps/ A ...
- android中使用gdbserver调试c程序
先废话两句,这篇文章可能有所缺陷,因为我并没有条件去测试动态库的调试.(主要是因为是自己的手机不想乱放各种so) 所以我只能说有空的话我会去进行下测试,这里就先这样了. 1.测试代码gdb-test. ...
- 基本的Bootstrap模板
<!DOCTYPE html> <html> <html lang="zh-cn"> <meta charset="UTF-8& ...
- jquery.mobile 中 collapsible-set collapsible listview 共同布局问题
最近项目用上了jquery.mobile这货,在手机上做点简单的显示.之前只知道有这个框架,没把玩过. 特别是事件绑定方面,相比桌面系统下浏览器用着各种不爽,不得要领. 如下图,在做后台系统时,一般左 ...