.net动态加载程序集和影子复制(Dynamic Load Assembly and Shadow Copy)
最近一值在学习.net动态加载程序集方面的内容。事情的起因是我有一个.net的服务程序想实现类似ASP.NET中更新bin文件夹下DLL无需重启IIS的功能,这个功能有个正式的名称叫影子复制(shadow copy)。因为在.NET中程序集无法卸载 ,所有我们把可能经常要变动的部分编译成一个单独的程序集并加载到新的应用程序域中(AppDomain)。下面是动态加载程序集的例子(例子使用VS2005)。
首先新建一个控制台应用程序项目,我取名叫 DynamicLoad,接着再新建另一个控制台应用程序项目,这里叫 AddIn。 DynamicLoad负责监控AddIn。

我们先来看看DynamicLoad中的Program.cs.
方法 watcher_Changed是一个事件代码,当监控的文件发生变化时就会执行该代码,有点奇怪的是一次文件变动会引发好几次该事件,所以要添加一些限制来保证一次文件变动只执行一次实际的重新加载操作。
下面是Loader.cs的代码。
最后是AddIn的代码,这里只是一个简单的打印输出,真实环境就是你运行具体代码的程序集。
运行的时候,把AddIn.exe文件复制到DynamicLoad.exe同目录中,然后运行DynamicLoad.exe,接着你可以修改一个AddIn,重新编译后把原DynamicLoad.exe目录中的AddIn.exe文件替换,这时程序就会自动加载新的AddIn.exe并执行,而不用先关闭DynamicLoad.exe,这样就实现了动态加载和影子复制的功能。
更多相关文章:
1. App Domains and dynamic loading
2. AppDomain shadowcopy explained
以上是我的理解,不当之处请指正。
首先新建一个控制台应用程序项目,我取名叫 DynamicLoad,接着再新建另一个控制台应用程序项目,这里叫 AddIn。 DynamicLoad负责监控AddIn。

我们先来看看DynamicLoad中的Program.cs.
1
using System;
2
using System.IO;
3
4
namespace DynamicLoad
5
{
6
class Program
7
{
8
//下面两个参数用来屏蔽同时发生好几个文件变动事件。
9
private static bool reloaded = false;
10
private static int previousSecond;
11
12
static void Main(string[] args)
13
{
14
Program.AddFileWatcher();
15
Loader loader = new Loader();
16
loader.CreateNewAppDomainAndRunAddIn();
17
18
Console.WriteLine("你可以尝试不关闭该程序的情况下更新AddIn程序。");
19
Console.ReadLine();
20
}
21
22
/// <summary>
23
/// 添加到文件的监控
24
/// </summary>
25
private static void AddFileWatcher()
26
{
27
FileSystemWatcher watcher = new FileSystemWatcher();
28
watcher.Path = AppDomain.CurrentDomain.BaseDirectory;
29
watcher.NotifyFilter = NotifyFilters.Size | NotifyFilters.CreationTime;
30
watcher.Filter = "*.exe";
31
watcher.Changed += new FileSystemEventHandler(watcher_Changed);
32
watcher.EnableRaisingEvents = true;
33
}
34
35
/// <summary>
36
/// 文件变动引发的事件
37
/// </summary>
38
private static void watcher_Changed(object sender, FileSystemEventArgs e)
39
{
40
//为了防止一次文件变动引发多次该事件,做了一些控制,只执行一次真正的重新加载操作。
41
//该方法不是非常精确,因为如果下面重新加载的时间超过1秒则会多加载一次。
42
int second = DateTime.Now.Second;
43
if (!reloaded && previousSecond != second)
44
{
45
reloaded = true;
46
previousSecond = second;
47
}
48
if (reloaded)
49
{
50
Console.WriteLine("文件 " + e.Name + " 发生变化。变化类型:" + e.ChangeType.ToString());
51
reloaded = false;
52
//卸载后重新生成应用程序域并执行程序集
53
Loader loader = new Loader();
54
loader.UnLoadApp();
55
loader.CreateNewAppDomainAndRunAddIn();
56
}
57
}
58
}
59
}
60
方法 AddFileWatcher监控当前AppDomain目录中的exe文件的变化。运行时要把AddIn.exe复制到DynimicLoad.exe同目录下。当然你也可以修改监控目录。
using System;2
using System.IO;3

4
namespace DynamicLoad5
{6
class Program7
{8
//下面两个参数用来屏蔽同时发生好几个文件变动事件。9
private static bool reloaded = false;10
private static int previousSecond;11
12
static void Main(string[] args)13
{14
Program.AddFileWatcher();15
Loader loader = new Loader();16
loader.CreateNewAppDomainAndRunAddIn();17

18
Console.WriteLine("你可以尝试不关闭该程序的情况下更新AddIn程序。");19
Console.ReadLine();20
}21

22
/// <summary>23
/// 添加到文件的监控24
/// </summary>25
private static void AddFileWatcher()26
{27
FileSystemWatcher watcher = new FileSystemWatcher();28
watcher.Path = AppDomain.CurrentDomain.BaseDirectory;29
watcher.NotifyFilter = NotifyFilters.Size | NotifyFilters.CreationTime;30
watcher.Filter = "*.exe";31
watcher.Changed += new FileSystemEventHandler(watcher_Changed);32
watcher.EnableRaisingEvents = true;33
}34

35
/// <summary>36
/// 文件变动引发的事件37
/// </summary>38
private static void watcher_Changed(object sender, FileSystemEventArgs e)39
{40
//为了防止一次文件变动引发多次该事件,做了一些控制,只执行一次真正的重新加载操作。41
//该方法不是非常精确,因为如果下面重新加载的时间超过1秒则会多加载一次。42
int second = DateTime.Now.Second;43
if (!reloaded && previousSecond != second)44
{45
reloaded = true;46
previousSecond = second;47
}48
if (reloaded)49
{50
Console.WriteLine("文件 " + e.Name + " 发生变化。变化类型:" + e.ChangeType.ToString());51
reloaded = false;52
//卸载后重新生成应用程序域并执行程序集53
Loader loader = new Loader();54
loader.UnLoadApp();55
loader.CreateNewAppDomainAndRunAddIn();56
}57
}58
}59
}60

方法 watcher_Changed是一个事件代码,当监控的文件发生变化时就会执行该代码,有点奇怪的是一次文件变动会引发好几次该事件,所以要添加一些限制来保证一次文件变动只执行一次实际的重新加载操作。
下面是Loader.cs的代码。
1
using System;
2
3
namespace DynamicLoad
4
{
5
class Loader
6
{
7
8
private static AppDomain domain;
9
private static int _currentNumber = 1;
10
11
/// <summary>
12
/// 新建一个应用程序域并执行某程序集
13
/// </summary>
14
public void CreateNewAppDomainAndRunAddIn()
15
{
16
AppDomainSetup setup = new AppDomainSetup();
17
setup.ApplicationName = "NewAppDomain";
18
setup.CachePath = System.Environment.CurrentDirectory;
19
setup.ShadowCopyFiles = "true";
20
21
domain = AppDomain.CreateDomain("domain id " + _currentNumber++, null, setup);
22
23
Console.WriteLine("新建domain:" + domain.FriendlyName);
24
25
domain.ExecuteAssembly("AddIn.exe");
26
}
27
28
/// <summary>
29
/// 卸载应用程序域
30
/// </summary>
31
public void UnLoadApp()
32
{
33
if (domain != null)
34
{
35
AppDomain.Unload(domain);
36
}
37
}
38
}
39
}
40
方法CreateNewAppDomainAndRunAddIn用来新建一个AppDomain并执行相应的程序集。其中AppDomainSetup类用来传递新建时的参数,如名字等。其中ShadowCopyFiles 这个参数必不可少。因为只有把该参数设为"true"才能开启影子复制的功能。关于如何实现影子复制这里有一文介绍。开启该功能才能在加载程序集时不锁定程序集文件,这样你就可以更新程序集了。 下面的UnLoadApp方法用来卸载整个AppDomain。卸载的时候.net会结束所有原AppDomain中的所有线程,可能这不是很好的方式,因为可能有些线程还没有执行完。比较好的方式可以把旧AppDomain的引用记录下来,等一段时间后再卸载。
using System;2

3
namespace DynamicLoad4
{5
class Loader6
{7

8
private static AppDomain domain;9
private static int _currentNumber = 1;10

11
/// <summary>12
/// 新建一个应用程序域并执行某程序集13
/// </summary>14
public void CreateNewAppDomainAndRunAddIn()15
{16
AppDomainSetup setup = new AppDomainSetup();17
setup.ApplicationName = "NewAppDomain";18
setup.CachePath = System.Environment.CurrentDirectory;19
setup.ShadowCopyFiles = "true";20

21
domain = AppDomain.CreateDomain("domain id " + _currentNumber++, null, setup);22
23
Console.WriteLine("新建domain:" + domain.FriendlyName);24

25
domain.ExecuteAssembly("AddIn.exe");26
}27

28
/// <summary>29
/// 卸载应用程序域30
/// </summary>31
public void UnLoadApp()32
{33
if (domain != null)34
{35
AppDomain.Unload(domain);36
}37
}38
}39
}40

最后是AddIn的代码,这里只是一个简单的打印输出,真实环境就是你运行具体代码的程序集。
1
using System;
2
3
namespace AddIn
4
{
5
class Program
6
{
7
static void Main(string[] args)
8
{
9
Console.WriteLine("执行AddIn.exe,时间:" + DateTime.Now.ToString());
10
}
11
}
12
}
13
这里很简单,执行时只打印一条语句。
using System;2

3
namespace AddIn4
{5
class Program6
{7
static void Main(string[] args)8
{9
Console.WriteLine("执行AddIn.exe,时间:" + DateTime.Now.ToString());10
}11
}12
}13

运行的时候,把AddIn.exe文件复制到DynamicLoad.exe同目录中,然后运行DynamicLoad.exe,接着你可以修改一个AddIn,重新编译后把原DynamicLoad.exe目录中的AddIn.exe文件替换,这时程序就会自动加载新的AddIn.exe并执行,而不用先关闭DynamicLoad.exe,这样就实现了动态加载和影子复制的功能。
更多相关文章:
1. App Domains and dynamic loading
2. AppDomain shadowcopy explained
以上是我的理解,不当之处请指正。


浙公网安备 33010602011771号