1. 什麼是MAF和MEF? MEF和MEF微軟官方介紹:https://learn.microsoft.com/zh-cn/dotnet/framework/mef/ MEF是輕量化的插件框架,MAF是複雜的插件框架。 因為MAF有進程隔離和程式域隔離可選。我需要插件進程隔離同時快速傳遞數據,最後 ...
1. 什麼是MAF和MEF?
MEF和MEF微軟官方介紹:https://learn.microsoft.com/zh-cn/dotnet/framework/mef/
MEF是輕量化的插件框架,MAF是複雜的插件框架。
因為MAF有進程隔離和程式域隔離可選。我需要插件進程隔離同時快速傳遞數據,最後選擇了MAF。
如果不需要真正的物理隔離還是建議使用簡單一點的MEF框架。
2. 如何學習MAF?
MAF其實是一項很老的技術,入門我看的是《WPF編程寶典》第32章 插件模型。裡面有MAF和MEF的詳細介紹和許多樣例。
但是要深入理解還是看了很多其他的東西,下麵我詳細說明,我自己理解和總結的MAF。
3. MAF框架入門
3.1 MAF框架構成與搭建
MAF框架模式是固定的,這裡做一個詳細介紹。
首先是要添加幾個新項目,下圖中不包含主項目。
Addin文件夾是放置插件用的,其餘都是必要項目。
假設HostView項目和主項目的輸出路徑是..\Output\
然後修改每個項目的輸出文件夾,例如AddInSideAdapter項目輸出路徑可以設置為..\Output\AddInSideAdapters\
註意插件項目輸出到Addin文件夾中的子文件夾是..\..\Output\AddIns\MyAddin\
最後項目的輸出文件夾結構是:
D:\Demo\Output\AddIns
D:\Demo\Output\AddInSideAdapters
D:\Demo\Output\AddInViews
D:\Demo\Output\Contracts
D:\Demo\Output\HostSideAdapters
來看看MAF框架模型構成。
上圖中綠色的是被引用藍色項目所引用。例如HostSideAdapter就要引用Contract和Hostview,如下圖所示。
註意引用時取消勾選複製本地。
這時就完成基本項目結構的搭建。
3.2 MAF框架實現
這裡想實現宿主項目和插件項目的雙向通信。即插件項目將相關函數介面在宿主實現,然後將宿主項目相關函數介面用委托類的方式註冊給插件項目。實現雙向通信。
用《WPF編程寶典》樣例代碼來說,樣例中,插件程式實現ProcessImageBytes處理圖像數據的函數,處理同時需要向宿主項目報告處理進度,宿主中 ReportProgress函數實現進度可視化。
MAF實現一般是先寫Contract協議,明確需要的函數介面。然後寫AddlnView和HostView。實際上這兩個是將函數介面抽象化,在介面里函數複製過來前面加 public abstract 就行。
之後HostSideAdapter和AddInSideAdapter直接快速實現介面。
首先從Contract開始,Contract是定義介面,需要設置對象標識符[AddInContract],且必須繼承IContract。
[AddInContract] public interface IImageProcessorContract : IContract { byte[] ProcessImageBytes(byte[] pixels); void Initialize(IHostObjectContract hostObj); } public interface IHostObjectContract : IContract { void ReportProgress(int progressPercent); }
Initialize函數是提供宿主函數註冊的介面。
然後在HostView和AddInView分別定義主程式和插件程式的介面抽象類。
public abstract class ImageProcessorHostView { public abstract byte[] ProcessImageBytes(byte[] pixels); public abstract void Initialize(HostObject host); } public abstract class HostObject { public abstract void ReportProgress(int progressPercent); }
註意AddlnView需要設置對象標識符[AddInBase]。
[AddInBase] public abstract class ImageProcessorAddInView { public abstract byte[] ProcessImageBytes(byte[] pixels); public abstract void Initialize(HostObject hostObj); } public abstract class HostObject { public abstract void ReportProgress(int progressPercent); }
之後在HostSideAdapter實現抽象類。
註意HostSideAdapter繼承HostView的抽象類,在構造函數里需設置ContractHandle插件生存周期,ContractHandle不能為readonly。
[HostAdapter] public class ImageProcessorContractToViewHostAdapter : HostView.ImageProcessorHostView { private Contract.IImageProcessorContract contract; private ContractHandle contractHandle; public ImageProcessorContractToViewHostAdapter(Contract.IImageProcessorContract contract) { this.contract = contract; contractHandle = new ContractHandle(contract); } public override byte[] ProcessImageBytes(byte[] pixels) { return contract.ProcessImageBytes(pixels); } public override void Initialize(HostView.HostObject host) { HostObjectViewToContractHostAdapter hostAdapter = new HostObjectViewToContractHostAdapter(host); contract.Initialize(hostAdapter); } } public class HostObjectViewToContractHostAdapter : ContractBase, Contract.IHostObjectContract { private HostView.HostObject view; public HostObjectViewToContractHostAdapter(HostView.HostObject view) { this.view = view; } public void ReportProgress(int progressPercent) { view.ReportProgress(progressPercent); } }
在AddInSideAdapter實現Contract介面,基本和HostSideAdapter類似,只是繼承的類不同。
[AddInAdapter] public class ImageProcessorViewToContractAdapter : ContractBase, Contract.IImageProcessorContract { private AddInView.ImageProcessorAddInView view; public ImageProcessorViewToContractAdapter(AddInView.ImageProcessorAddInView view) { this.view = view; } public byte[] ProcessImageBytes(byte[] pixels) { return view.ProcessImageBytes(pixels); } public void Initialize(Contract.IHostObjectContract hostObj) { view.Initialize(new HostObjectContractToViewAddInAdapter(hostObj)); } } public class HostObjectContractToViewAddInAdapter : AddInView.HostObject { private Contract.IHostObjectContract contract; private ContractHandle handle; public HostObjectContractToViewAddInAdapter(Contract.IHostObjectContract contract) { this.contract = contract; this.handle = new ContractHandle(contract); } public override void ReportProgress(int progressPercent) { contract.ReportProgress(progressPercent); } }
宿主項目中需要實現HostView里HostObject抽象類。
private class AutomationHost : HostView.HostObject { private ProgressBar progressBar; public AutomationHost(ProgressBar progressBar) { this.progressBar = progressBar; } public override void ReportProgress(int progressPercent) { // Update the UI on the UI thread. progressBar.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate() { progressBar.Value = progressPercent; } ); } }
然後是在宿主項目里激活插件,並初始化AutomationHost。
string path = Environment.CurrentDirectory; AddInStore.Update(path);//更新目錄中Addins目錄里的插件 IList<AddInToken> tokens = AddInStore.FindAddIns(typeof(HostView.ImageProcessorHostView), path);//查找全部插件 lstAddIns.ItemsSource = tokens;//插件可視化 AddInToken token = (AddInToken)lstAddIns.SelectedItem;//選擇插件 AddInProcess addInProcess = new AddInProcess();//創建插件進程 addInProcess.Start();//激活插件進程 addin = token.Activate<HostView.ImageProcessorHostView>(addInProcess,AddInSecurityLevel.Internet);//激活插件 //如果只是想隔離程式域,就無需創建AddInProcess,激活插件如下 // HostView.ImageProcessorHostView addin = token.Activate<HostView.ImageProcessorHostView>(AddInSecurityLevel.Host); automationHost = new AutomationHost(progressBar);//創建AutomationHost類 addin.Initialize(automationHost);//初始化automationHost
插件項目中實現AddInView中的抽象類。
[AddIn("Negative Image Processor", Version = "1.0", Publisher = "Imaginomics",Description = "")] public class NegativeImageProcessor : AddInView.ImageProcessorAddInView { public override byte[] ProcessImageBytes(byte[] pixels) { int iteration = pixels.Length / 100; for (int i = 0; i < pixels.Length - 2; i++) { pixels[i] = (byte)(255 - pixels[i]); pixels[i + 1] = (byte)(255 - pixels[i + 1]); pixels[i + 2] = (byte)(255 - pixels[i + 2]); if (i % iteration == 0) { host?.ReportProgress(i / iteration); } } return pixels; } private AddInView.HostObject host; public override void Initialize(AddInView.HostObject hostObj) { host = hostObj; }
這時宿主可以把數據傳遞給插件程式,插件程式中ProcessImageBytes處理數據然後通過host?.ReportProgress(i / iteration);向宿主傳遞消息。
這裡有提供樣常式序。
4. MAF框架常見問題
4.1手動關閉插件
AddInController addInController = AddInController.GetAddInController(addIn);
addInController.Shutdown();
此方法適應於非應用隔離的手動關閉。對於應用隔離式插件,用此方法會拋出異常。
如上面樣例就是應用隔離的插件,可以根據進程id直接關閉進程。
public void ProcessClose() { try { if (process != null) { Process processes = Process.GetProcessById(addInProcess.ProcessId); if (processes?.Id > 0) { processes.Close(); } } } catch (Exception) { } }
4.2 插件異常
System.Runtime.Remoting.RemotingException: 從 IPC 埠讀取時失敗: 管道已結束。這是插件最常見的異常,因為插件拋出異常而使得插件程式關閉。
如果是插件調用非托管代碼,而產生的異常,可以查Windows應用程式日誌來確定異常。其餘能捕獲的異常儘量捕獲保存到日誌,方便查看。
4.3 雙向通信
實際應用過程中,往往是通過委托來將宿主相關函數暴露給一個類,然後通過在宿主程式初始化後。在插件中實例化後就可以直接調用宿主的相關函數,反之同理。
這裡是通過委托暴露宿主的一個函數。
public delegate void UpdateCallBack(string message, bool isclose, int leve); public class VideoHost : HostAddInView { public event UpdateCallBack Updatecallback; public override void ProcessVideoCallBack(string message, bool isclose, int leve) { Updatecallback?.Invoke(message, isclose, leve); } }
在插件程式中實例化後調用。
private HostAddInView hostAddInView; public override void Initialize(HostAddInView hostAddInView) { this.hostAddInView = hostAddInView; } private void ErrorCallback(string message, bool isclose, int leve) { hostAddInView?.ProcessVideoCallBack(message, isclose, leve); }
5. MAF深入理解
MAF本質是實現IpcChannel通信,在一個期刊中有作者拋棄MAF固定結構自己實現IpcChannel,因為代碼很複雜,就不在此詳細闡述。
如果要實現應用域隔離,自己實現IpcChannel,MAF中的應用域隔離實現也是非常好的參考資料。
MAF的7層結構主要是實現從插件的宿主函數轉換,例如可以在將插件程式的界面放入主界面中渲染,做出像瀏覽器一樣的開一個界面就是一個進程。將插件中的組件在AddInSideAdapter中轉換為Stream然後在HostSideAdapter中將Stream實例化為組件。而HostView和AddInView實際上是提供兩個轉換介面,Contract是定義傳輸介面。
另外如果傳輸插件向數組傳遞圖像數據,最後是轉換成byte[],或者使用共用記憶體。
如果有什麼遺漏和錯誤,歡迎指正,批評。