65.9K
CodeProject 正在变化。 阅读更多。
Home

动态加载 .NET 程序集

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.52/5 (12投票s)

2013年10月10日

MIT

3分钟阅读

viewsIcon

97832

downloadIcon

4236

C# 中动态程序集加载的示例实现

引言

在原生 C++ 中,动态库加载并不是什么新鲜事物。 在 Microsoft Windows 的实现中,此功能通过使用 动态链接库 (DLL)来实现。此功能极大地扩展了应用程序的灵活性,其中新功能可以以 插件 的形式添加/修改,而无需重新编译整个应用程序。.NET 框架提供了类似的功能来动态加载程序集,这使得基于 .NET 的应用程序可以进行插件开发。此外,.NET 框架提供的动态程序集加载比原生 C++ DLL 强大得多。

维基百科:插件是一种软件组件,可为现有软件应用程序添加特定功能。

概述

只有当应用程序和组件使用相同的语言时,才能进行动态加载。 在这种情况下,应用程序和插件组件需要使用约定的接口相互通信。 此接口的内容在其整个生命周期内都不能更改,修改此接口的内容需要完全重新编译应用程序和所有组件。 请参见下图

实现

创建了一个动态程序集加载项目示例,如下所示以供进一步讨论。 此示例用 C# 编写,使用 Visual Studio 2010,其中应用程序和插件程序集都引用了PluginInterface.dll

  • DynamicLoadAssembly:创建用于测试 .NET 框架中动态加载能力的应用程序
  • PluginInterface:连接应用程序和插件程序集的桥梁
  • Plugin_SumPlugin_MultiplierPlugin_Calculation:实现 PluginInterface.dllIPlugin 接口的插件程序集

演示应用程序包含两个部分:简单测试和高级测试,其中简单测试将从定义的程序集加载,而高级测试将发现并加载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)
© . All rights reserved.