(原創)[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,互不影響且正常調用的。

相信看完的你,一定會有所收穫!

本文地址://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#》,曾經看過,但走馬觀花、不知所云、無甚收穫。此次重看,發現已可以讀懂,頗有感獲。
在看到了強簽名時,驀然發現,這不就是曾經困擾我很久的解決方法嗎?
既心動便行動,經過一番測試,果然可行。
挺感慨的,果然往基礎的方向去學習,是正確的。

本人水平有限,難免有所疏漏,歡迎各位讀者評論指正。


五、源代碼下載

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


-【END】-

Tags: