[C#]插件編程框架 MAF 開發總結

来源:https://www.cnblogs.com/mrf2233/archive/2023/05/26/17434368.html
-Advertisement-
Play Games

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);向宿主傳遞消息。

這裡有提供樣常式序。

項目附件.7z

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[],或者使用共用記憶體。

如果有什麼遺漏和錯誤,歡迎指正,批評。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本節我們看看Netty的傳輸(全是乾貨,自帶水杯 # 一、Java的NIO和OIO 流經網路的數據總是具有相同的類型:位元組。這些位元組是如何流動的主要取決於我們所說的網路傳輸。 ## 1.1 OIO 我們先來看一段Java的阻塞應用程式程式: ```java package com.example.j ...
  • Timescaledb 在物聯網時代,出現了大量以時間為中心海量產生的感測器數據,稱為時序數據。這類數據的特點是: 數據記錄總有一個時間戳。 數據幾乎總是追加,不更新也不刪除。 大量使用近期的數據。很少更新或者回填時間間隔的缺失數據。 與時間間隔頻率關係不大。但累積的數據量大,可能會有峰值。 對這類 ...
  • # 1.編寫hello world項目 編程界每種語言的第一個程式往往都是輸出hello world。因此我們來看看,如何用Python輸出hello world。 1.如果你是初學者,main.py中的代碼暫時是無法看懂的,所以可以把main中的源代碼直接刪除。如下所示 ![image](http ...
  • 作者:nyingping\ 來源:juejin.cn/post/7215886869199863869 > 記者:大爺您有什麼特長呀? > > FastJson:我很快。 > > 記者:23423 乘以 4534 等於多少? > > FastJson:等於 2343. > > 記者:?? > > F ...
  • 面試題==知識點,這裡所記錄的面試題並不針對於面試者,而是將這些面試題作為技能知識點來看待。不以刷題進大廠為目的,而是以學習為目的。這裡的知識點會持續更新,目錄也會隨時進行調整。 ...
  • 簡訊在實現的邏輯上,也遵循消息中心的基礎設計,即消息生產之後,通過消息中心進行投遞和消費,屬於典型的生產消費模型; ...
  • # 1.新建python項目 1. 在編寫程式之前,我們需要新建一個項目(Project),在桌面雙擊PyCharm的快捷方式![image](https://img2023.cnblogs.com/blog/3179433/202305/3179433-20230526074257088-7431 ...
  • # WPF佈局基礎 ## WPF的佈局原則 + 一個視窗中只能包含一個元素 + 不應顯示設置元素尺寸 + 不應使用坐標設置元素的位置 + 可以嵌套佈局容器 ## WPF有哪些佈局容器? + Grid:網格。可以自定義行和列並通過行列的數量、行高和列寬來調整控制項的佈局。近似於HTML中的Table。 ...
一周排行
    -Advertisement-
    Play Games
  • 在本篇教程中,我們學習瞭如何使用 Taurus.MVC WebMVC 框架創建一個簡單的頁面。 我們創建了一個控制器並編寫了一個用於呈現頁面的方法,然後創建了對應的視圖,並最終成功運行了應用程式。 在下一篇教程中,我們將繼續探索 Taurus.MVC WebMVC 框架的更多功能和用法。 ...
  • 一:背景 1. 講故事 很多.NET開發者在學習高級調試的時候,使用sos的命令輸出會發現這裡也看不懂那裡也看不懂,比如截圖中的這位朋友。 .NET高級調試屬於一個偏冷門的領域,國內可觀測的資料比較少,所以很多東西需要你自己去探究源代碼,然後用各種調試工具去驗證,相關源代碼如下: coreclr: ...
  • 我一直都以為c中除以2的n次方可以使用右移n位代替,然而在實際調試中發現並不都是這樣的。是在計算餘數是發現了異常 被除數:114325068 右移15計算結果:3488 除法取整計算結果:3489 右移操作計算餘數:33772 除法取整計算餘數:1005 顯然:這是不一樣的。 移位操作是一條cpu指 ...
  • 在上一篇文章中,我們介紹了ReentrantLock類的一些基本用法,今天我們重點來介紹一下ReentrantLock其它的常用方法,以便對ReentrantLock類的使用有更深入的理解。 ...
  • Excelize 是 Go 語言編寫的用於操作電子錶格辦公文檔的開源基礎庫,2024年2月26日,社區正式發佈了 2.8.1 版本,該版本包含了多項新增功能、錯誤修複和相容性提升優化。 ...
  • 雲採用框架(Cloud Adoption Framework,簡稱CAF)為企業上雲提供策略和技術的指導原則和最佳實踐,幫助企業上好雲、用好雲、管好雲,併成功實現業務目標。本雲採用框架是基於服務大量企業客戶的經驗總結,將企業雲採用分為四個階段,並詳細探討企業應在每個階段採取的業務和技術策略;同時,還 ...
  • 與TXT文本文件,PDF文件更加專業也更適合傳輸,常用於正式報告、簡歷、合同等場合。項目中如果有使用Java將TXT文本文件轉為PDF文件的需求,可以查看本文中介紹的免費實現方法。 免費Java PDF庫 本文介紹的方法需要用到Free Spire.PDF for Java,該免費庫支持多種操作、轉 ...
  • 指針和引用 當我們需要在程式中傳遞變數的地址時,可以使用指針或引用。它們都可以用來間接訪問變數,但它們之間有一些重要的區別。 指針是一個變數,它存儲另一個變數的地址。通過指針,我們可以訪問存儲在該地址中的變數。指針可以被重新分配,可以指向不同的變數,也可以為NULL。指針使用*運算符來訪問存儲在地址 ...
  • 即使再小再簡單的需求,作為研發開發完畢之後,我們可以直接上線麽?其實很多時候事故往往就是由於“不以為意”發生的。事故的發生往往也遵循“墨菲定律”,這就要求我們更要敬畏線上,再小的需求點都需要經過嚴格的測試驗證才能上線。 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一、是什麼 許可權是對特定資源的訪問許可,所謂許可權控制,也就是確保用戶只能訪問到被分配的資源 而前端許可權歸根結底是請求的發起權,請求的發起可能有下麵兩種形式觸發 頁面載入觸發 頁面上的按鈕點擊觸發 總的來說,所有的請求發起都觸發自前端路由或 ...