动态加载 .NET 程序集
C# 中动态程序集加载的示例实现
引言
在原生 C++ 中,动态库加载并不是什么新鲜事物。 在 Microsoft Windows 的实现中,此功能通过使用 动态链接库 (DLL)来实现。此功能极大地扩展了应用程序的灵活性,其中新功能可以以 插件 的形式添加/修改,而无需重新编译整个应用程序。.NET 框架提供了类似的功能来动态加载程序集,这使得基于 .NET 的应用程序可以进行插件开发。此外,.NET 框架提供的动态程序集加载比原生 C++ DLL 强大得多。
维基百科:插件是一种软件组件,可为现有软件应用程序添加特定功能。
概述
只有当应用程序和组件使用相同的语言时,才能进行动态加载。 在这种情况下,应用程序和插件组件需要使用约定的接口相互通信。 此接口的内容在其整个生命周期内都不能更改,修改此接口的内容需要完全重新编译应用程序和所有组件。 请参见下图
实现
创建了一个动态程序集加载项目示例,如下所示以供进一步讨论。 此示例用 C# 编写,使用 Visual Studio 2010,其中应用程序和插件程序集都引用了PluginInterface.dll。
DynamicLoadAssembly
:创建用于测试 .NET 框架中动态加载能力的应用程序PluginInterface
:连接应用程序和插件程序集的桥梁Plugin_Sum
、Plugin_Multiplier
、Plugin_Calculation
:实现 PluginInterface.dll 中IPlugin
接口的插件程序集
演示应用程序包含两个部分:简单测试和高级测试,其中简单测试将从定义的程序集加载,而高级测试将发现并加载Plugins文件夹中选定的程序集。 此外,创建一个名为“NewPlugin
”的构建配置,以模拟插件开发,在这种情况下,无需重新编译主应用程序即可创建新插件。
IPlugin 接口
IPlugin
接口与任何普通接口没有什么不同。 它可能包含属性、方法(函数)和事件。 由于主应用程序无法查看插件程序集中实现的类,因此 IPlugin
是应用程序和插件程序集之间唯一的访问方式。
public interface IPlugin
{
string Name {get; }
string GetDescription();
double GetLastResult { get; }
double Execute(double value1, double value2);
event EventHandler OnExecute;
void ExceptionTest(string input);
}
加载程序集
正如您所看到的,除了 PluginInterface
之外,主应用程序不引用插件程序集。 程序集加载是通过使用 System.Reflection
命名空间中的 LoadAssembly
完成的。 在此示例中,我们假设每个插件程序集仅包含一个实现 IPlugin
接口的类。 从理论上讲,一个程序集中可以有多个类实现 IPlugin
接口,我们将其留给您进一步探索。
private PluginInterface.IPlugin LoadAssembly(string assemblyPath)
{
string assembly = Path.GetFullPath(assemblyPath);
Assembly ptrAssembly = Assembly.LoadFile(assembly);
foreach (Type item in ptrAssembly.GetTypes())
{
if (!item.IsClass) continue;
if (item.GetInterfaces().Contains(typeof(PluginInterface.IPlugin)))
{
return (PluginInterface.IPlugin)Activator.CreateInstance(item);
}
}
throw new Exception("Invalid DLL, Interface not found!");
}
一旦程序集成功加载到应用程序中,它就可以像正常的类实例一样使用,无论它是函数调用还是事件订阅。 加载的程序集上的事件订阅示例如下所示
currPlugin = LoadAssembly(".\\Plugins\\" + cbAssemblies.Text + ".dll");
currPlugin.OnExecute += new EventHandler(currPlugin_OnExecute); //Subscribe Event.
局限性
尽管动态程序集加载具有很高的灵活性,但它存在一个限制,即在主应用程序终止之前,无法卸载加载的程序集,即使使用 System.AppDomain
在调用 AppDomain.Unload
后也不会释放加载的程序集。
异常处理
异常可以毫无问题地从加载的程序集传递到主应用程序。 相同的 try
... catch
块足以处理程序集引发的异常,这与原生 C++ DLL 不同,原生 C++ DLL 通常以错误代码和错误消息的形式返回错误。
历史
- 2013年10月10日:初始版本 (版本 1.0.0)