• 如何从程序集中加载及卸载插件(下)


    继续我们上一章的讲解。现在我们用一个具体的程序示例来演示我们插件的加载及卸载过程,我们先回顾一下上一章中我们总结出来的一些思路:
     

    1. 建立一个新的AppDomain: AppDomain.CreateDomain()。
    2. 利用的AppDomain的实例,采用 CreateInstanceFromAndUnwrap() 方法在新的AppDomain中构建一个指定的类型,并返回相应的Proxy。
    3. 根据获取的Proxy就可调用插件了。
    4. 卸载时,完成一些资源清理后可以直接对新建出的AppDomain进行UnLoad


    我们先来准备一下Visual Studio中的解决方案:

     
     

    在上图的解决方案中,我们新建了三个工程,来看看他们具体的职责:

    1. Unit9.Contract:包含插件的调用接口
    2. Unit9.Implement:包含插件具体的实现
    3. Unit9.App:主程序的代码,动态加载插件,并依据插件的调用接口对插件进行调用。

    为什么要做这样的区分呢?因为对于主程序而言,它可以在运行时动态的加载多个插件,但主程序并不需要知道插件内部的具体实现细节。主程序只需要知道被调用插件相应的接口即可完成调用,因此我们有必要将调用规范及插件的具体实现分开。

    插件的调用接口代码
    1. namespace Unit9   
    2. {   
    3.     public interface IAddin   
    4.     {   
    5.         string Run(string paramString);   
    6.     }   
    7. }  

    定义出来上面的调用接口,我们接下来做插件功能的具体实现。在上章的讲述中,我们总结出了插件的实现必须符合两个规范:

    1. 必须继承自MarshalByRefObject。
    2. 插件中传递的参数类型及返回值必须是可序列化的。

    因此依此来做具体的实现:

    插件的实现代码
    1. using System;   
    2.   
    3. namespace Unit9   
    4. {   
    5.     public class AddinOne:MarshalByRefObject,IAddin   
    6.     {   
    7.         public string Run(string paramString)   
    8.         {   
    9.             const string resultString =   
    10.                 "Current AppDomain:{0},Param String:{1}!";   
    11.   
    12.             return string.Format(   
    13.                 resultString,   
    14.                 AppDomain.CurrentDomain.FriendlyName,   
    15.                 paramString);   
    16.         }   
    17.     }   
    18. }  

    在上面的代码中,我们输出了一个AppDomain的信息。如前面所讲的,我们需要在一个新的AppDomain中加载插件的实现,因此这个AppDomain应该与系统默认的AppDomain应该是不相同的。接下来我们来看看主程序的代码:

    C#代码
    1. using System;   
    2. using System.IO;   
    3. using System.Runtime.Remoting.Lifetime;   
    4.   
    5. namespace Unit9   
    6. {   
    7.     class Program   
    8.     {   
    9.         /// <summary>   
    10.         /// 构建一个AppDomainSetup实例   
    11.         /// 用于启用卷影复制并设置基本路径   
    12.         /// </summary>   
    13.         public static AppDomainSetup CreateAppDomainSetup()   
    14.         {   
    15.             AppDomainSetup setup = new AppDomainSetup();   
    16.             setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;   
    17.             setup.ShadowCopyFiles = "true";   
    18.             return setup;   
    19.         }   
    20.   
    21.   
    22.         /// <summary>   
    23.         /// 从当前目录下的指定的程序集文件中加载指定的类型   
    24.         /// </summary>   
    25.         public static object CreateAndUnwrap(AppDomain appDomain, string assemblyFile, string typeName)   
    26.         {   
    27.             string fullName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, assemblyFile);   
    28.             return appDomain.CreateInstanceFromAndUnwrap(fullName, typeName);   
    29.         }   
    30.   
    31.            
    32.         static void Main()   
    33.         {   
    34.             Console.WriteLine("Current AppDomain:{0}",    
    35.                 AppDomain.CurrentDomain.FriendlyName);   
    36.   
    37.   
    38.             AppDomainSetup setup = CreateAppDomainSetup();   
    39.   
    40.             //建立准备加载插件的AppDomain   
    41.             AppDomain secAppDomain = AppDomain.CreateDomain("SecAppDomain"null, setup);   
    42.   
    43.   
    44.             //忽略新建立的AppDomain里面的调用租约管理   
    45.             secAppDomain.DoCallBack(delegate  
    46.             {   
    47.                 LifetimeServices.LeaseTime = TimeSpan.Zero;   
    48.             });   
    49.   
    50.             IAddin addinOne = (IAddin) CreateAndUnwrap(   
    51.                                            secAppDomain, "Unit9.Implement.dll""Unit9.AddinOne");   
    52.   
    53.             Console.WriteLine(addinOne.Run("Test"));   
    54.   
    55.   
    56.             //卸载装入插件的AppDomain   
    57.             AppDomain.Unload(secAppDomain);   
    58.   
    59.             //由于插件所在的AppDmain已被卸载,所以以下的执行会出现异常   
    60.             try  
    61.             {   
    62.                 Console.WriteLine(addinOne.Run("Test"));   
    63.             }   
    64.             catch(Exception ex)   
    65.             {   
    66.                 Console.WriteLine("发生调用异常:"+ex.Message);   
    67.             }   
    68.                
    69.             Console.ReadLine();   
    70.         }   
    71.     }   
    72. }   

    如果大家对于AppDomain及Remoting有一定了解的话,上面的代码不难理解。我们建立了一个新的AppDomain,并开启了这个AppDomain的卷影复制功能,卷影复制功能其实是不一定需要的,只是这样做的话可以让程序运行时不至少锁住相关的程序集文件。接下来,我们取消了对新建立的AppDomain的租约管理,因为我们的插件是一个长效的服务。再接着,我们从指定的插件实现文件中加载了指定的服务,如果在这真正的插件架构现中,这个过程可以进行配置化的。我们看看输出的结果:


    本文首发于:http://www.rogertong.cn/article.asp?id=24

    如果大家对插件感兴趣的话,请阅读我们Mussel框架相关的文章


    点击下载程序源文件

  • 相关阅读:
    linux 下 设置 MySQL8 表名大小写不敏感方法,解决设置后无法启动 MySQL 服务的问题
    JavaWeb入门_模仿天猫整站Tmall_JavaEE实践项目
    flowable工作流笔记
    bladex前端反向代理(解决跨域)
    Long类型传值前端精度丢失
    blade普通字典关联
    一些东西
    java面试题经典解读
    html元素定位原理
    行转换为列
  • 原文地址:https://www.cnblogs.com/isuper/p/1241300.html
Copyright © 2020-2023  润新知