• (原创)[C#] MEF 主程序与插件加载不同版本的DLL


    一、前言

    MEF(Managed Extensibility Framework),是轻量级的插件框架。使用简单,功能强大。详细介绍见MSDN,本文不再赘述。
    在使用MEF时,会遇到这样一种场景:
    主程序和插件都引用了同一个DLL中同一个【方法F】,但是引用的DLL版本不一致。

    那么,程序在运行时,会出现4种情况:
    (注:文字描述不太直观,可参照下节的实际演示)
    1,不同版本DLL中【方法F】未做改变:插件可正常调用【方法F】。
    2,不同版本DLL中【方法F】内部实现做了改变:引用了与主程序所引用的DLL版本不一致的插件,在调用【方法F】时,调用的不是插件所引用的DLL版本中的【方法F】,而是调用的主程序所引用的DLL版本中的【方法F】。
    3,不同版本DLL中【方法F】增加了重载方法:如果主程序所引用的DLL版本是包含重载方法的(即主程序所引用的DLL版本比插件引用的DLL版本),那么插件都可以正常调用,不过调用的【方法F】来自主程序所引用的DLL版本中的【方法F】;相反,如果主程序所引用的DLL版本是未包含重载方法的(即主程序所引用的DLL版本比插件引用的DLL版本),那么,那些调用了【重载方法F】的插件在运行时将会报错。
    4,不同版本DLL中【方法F】发生了改变——增减参数、改变返回值类型、删除了方法等:引用了与主程序所引用的DLL版本不一致的插件,在调用【方法F】时会报错。

    本篇文章,就是来讲一下如何实现主程序与插件们各自调用各自版本的DLL,互不影响且正常调用的。

    相信看完的你,一定会有所收获!

    本文地址:https://www.cnblogs.com/lesliexin/p/16280161.html


    二、问题复现

    (一)代码结构

    为了方便演示,我们创建一个简单的MEF程序,其结构如下:

    image

    其中:

    1,接口

    接口类很简单,包含一个接口定义和一个自定义的MEF导出特性标签。

    a,接口定义中,只包含了一个方法:Run();

    image

    b,自定义的MEF导出特性标签,主要是为MEF插件添加一个标记,方便对应到具体插件。

    image

    2,公共DLL

    公共DLL类,即复现场景时,主程序和插件们都需要引用的类。
    因为要生成不同版本的公共DLL,所以我们依次修改代码,并在生成属性中设置版本号,然后编译生成DLL。
    依次修改4次,共计4个版本的公共DLL:v1.0、v2.0、v3.0、v4.0,其代码修改如下:

    v1.0

    image

    v2.0

    image

    v3.0

    image

    v4.0

    image

    3,插件

    这些插件除了引用的公共DLL版本不一致外,基本方法都是一样的:继承并实现接口。
    这里由于“公共DLL v3.0”中对方法进行了重载,所以我们这里用两个插件来分别调用每一方法。

    插件1”代码:

    image

    插件2”代码:

    image

    插件3”代码:

    image

    插件4”代码:

    image

    插件5”代码:

    image

    4,主程序

    主程序的界面设计如下:

    image

    其中:
    “主程序”按钮作用:直接调用公共DLL中的方法。
    “插件1” - “插件4” 按钮作用:调用插件中的方法。

    因为要复现场景,所以主程序也需要生成多个版本。
    又因为在公共DLL v3.0中重载了方法,所以多分一个版本,来分别调用这两个方法。
    所以最终会生成5个版本的主程序。
    最终生成的文件结构如下:

    image

    主程序的代码,主要分为3部分:

    3.1,加载MEF插件

    在程序启动时,我们需要加载所有插件。

    image

    3.2,调用插件方法

    因为所有的插件都是基于统一的接口,所以我们先写一个通用的调用插件方法,然后在点击按钮时,直接传入插件对应导出标记即可。

    image

    3.3,主程序调用公共DLL方法

    对公共DLL的方法调用与插件并无二致,5个版本的主程序,其代码变化如下:

    v1.0

    image

    v2.0

    image

    v3.0

    image

    v4.0

    image

    v5.0

    image

    (二)演示

    我们依次编译生成不同版本的主程序,然后依次运行,其结果如下:

    v1.0

    image

    v2.0

    image

    v3.0

    image

    v4.0

    image

    v5.0

    image

    可以发现,当月主程序与插件都引用相同的公共DLL后,无论插件引用的公共DLL版本是多少,调用的均是主程序所引用的公共DLL版本。
    当插件与主程序引用的公共DLL中方法发生改变(如增减参数、修改返回值、删除了方法等),插件将会调用失败,抛出异常。


    三、解决方案

    问题已复现,那么我们该如何解决呢?
    解决目标就是主程序与插件们,各自调用各自所引用版本的“公共DLL”。在本示例中,就是:插件1引用“公共DLL v1.0”,插件2引用“公共DLL v2.0”,等等。

    而解决方案非常之简单,简单到一句话就能说完:
    为公共DLL添加强签名

    关于“强签名”,本文不再赘述,请参考MSDN。
    下面,我们来对示例进行修改。

    (一)添加强签名

    我们在公共DLL上右键,选择“属性”,然后选择“签名”,按提示添加强签名即可。

    image

    (二)重新生成插件和主程序。

    我们依次重新生成插件,和主程序,过程不再赘述。

    (三)演示

    我们重新依次运行5个版本的主程序,会发现问题已解决,主程序与插件们都各自调用了自己所引用版本的公共DLL。
    其与未进行强签名时的运行结果对比如下:

    v1.0

    image

    v2.0

    image

    v3.0

    image

    v4.0

    image

    v5.0

    image


    四、总结

    说实话,本篇文章所描述的问题,虽然不难,解决办法也很简单,却曾经困扰了我好久。
    之前一直没有找到现成的可行解决方案,曾在博问上提问过,答案虽然有用,但无奈不知怎么去应用。这事也就放下了。

    最近在看《CLR via C#》,曾经看过,但走马观花、不知所云、无甚收获。此次重看,发现已可以读懂,颇有感获。
    在看到了强签名时,蓦然发现,这不就是曾经困扰我很久的解决方法吗?
    既心动便行动,经过一番测试,果然可行。
    挺感慨的,果然往基础的方向去学习,是正确的。

    本人水平有限,难免有所疏漏,欢迎各位读者评论指正。


    五、源代码下载

    https://files.cnblogs.com/files/lesliexin/MEFDemo.zip


    -【END】-

  • 相关阅读:
    小程序底部弹出式导航菜单
    Markdown-it-latex2img
    Typography convention
    排版规约
    Java String的相关性质分析
    switch表达式中可以用哪些类型
    break,continue和return的区别
    一个.java文件中有多少个类(不是内部类)?
    RabbitMQ启动配置中出现(ArgumentError) argument error xxx的错误
    问题:IDEA部署Springboot项目的时候,提示很多xxx程序包找不到
  • 原文地址:https://www.cnblogs.com/lesliexin/p/16280161.html
Copyright © 2020-2023  润新知