翼度科技»论坛 编程开发 .net 查看内容

[C#]插件编程框架 MAF 开发总结

7

主题

7

帖子

21

积分

新手上路

Rank: 1

积分
21
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。
  1. [AddInContract]
  2.     public interface IImageProcessorContract : IContract
  3.     {
  4.         byte[] ProcessImageBytes(byte[] pixels);
  5.         void Initialize(IHostObjectContract hostObj);
  6.     }
  7.     public interface IHostObjectContract : IContract
  8.     {
  9.         void ReportProgress(int progressPercent);
  10.     }
复制代码
Initialize函数是提供宿主函数注册的接口。
 然后在HostView和AddInView分别定义主程序和插件程序的接口抽象类。
  1. public abstract class ImageProcessorHostView
  2.     {
  3.         public abstract byte[] ProcessImageBytes(byte[] pixels);
  4.         public abstract void Initialize(HostObject host);
  5.     }
  6.     public abstract class HostObject
  7.     {
  8.         public abstract void ReportProgress(int progressPercent);
  9.     }
复制代码
注意AddlnView需要设置对象标识符[AddInBase]。
  1. [AddInBase]
  2.     public abstract class ImageProcessorAddInView
  3.     {
  4.         public abstract byte[] ProcessImageBytes(byte[] pixels);
  5.         public abstract void Initialize(HostObject hostObj);
  6.     }
  7.     public abstract class HostObject
  8.     {
  9.         public abstract void ReportProgress(int progressPercent);
  10.     }
复制代码
之后在HostSideAdapter实现抽象类。
注意HostSideAdapter继承HostView的抽象类,在构造函数里需设置ContractHandle插件生存周期,ContractHandle不能为readonly。
  1.   [HostAdapter]
  2.     public class ImageProcessorContractToViewHostAdapter : HostView.ImageProcessorHostView
  3.     {
  4.         private Contract.IImageProcessorContract contract;
  5.         private ContractHandle contractHandle;
  6.         public ImageProcessorContractToViewHostAdapter(Contract.IImageProcessorContract contract)
  7.         {            
  8.             this.contract = contract;
  9.             contractHandle = new ContractHandle(contract);
  10.         }              
  11.         public override byte[] ProcessImageBytes(byte[] pixels)
  12.         {
  13.             return contract.ProcessImageBytes(pixels);
  14.         }
  15.         public override void Initialize(HostView.HostObject host)
  16.         {            
  17.             HostObjectViewToContractHostAdapter hostAdapter = new HostObjectViewToContractHostAdapter(host);
  18.             contract.Initialize(hostAdapter);
  19.         }
  20.     }
  21.     public class HostObjectViewToContractHostAdapter : ContractBase, Contract.IHostObjectContract
  22.     {
  23.         private HostView.HostObject view;
  24.         public HostObjectViewToContractHostAdapter(HostView.HostObject view)
  25.         {
  26.             this.view = view;
  27.         }
  28.         public void ReportProgress(int progressPercent)
  29.         {
  30.             view.ReportProgress(progressPercent);
  31.         }        
  32.     }
复制代码
 
在AddInSideAdapter实现Contract接口,基本和HostSideAdapter类似,只是继承的类不同。
  1. [AddInAdapter]
  2.     public class ImageProcessorViewToContractAdapter : ContractBase, Contract.IImageProcessorContract
  3.     {
  4.         private AddInView.ImageProcessorAddInView view;
  5.         public ImageProcessorViewToContractAdapter(AddInView.ImageProcessorAddInView view)
  6.         {
  7.             this.view = view;
  8.         }
  9.         public byte[] ProcessImageBytes(byte[] pixels)
  10.         {
  11.             return view.ProcessImageBytes(pixels);
  12.         }
  13.         public void Initialize(Contract.IHostObjectContract hostObj)
  14.         {            
  15.             view.Initialize(new HostObjectContractToViewAddInAdapter(hostObj));            
  16.         }
  17.     }
  18.     public class HostObjectContractToViewAddInAdapter : AddInView.HostObject
  19.     {
  20.         private Contract.IHostObjectContract contract;
  21.         private ContractHandle handle;
  22.         public HostObjectContractToViewAddInAdapter(Contract.IHostObjectContract contract)
  23.         {
  24.             this.contract = contract;
  25.             this.handle = new ContractHandle(contract);            
  26.         }
  27.                
  28.         public override void ReportProgress(int progressPercent)
  29.         {
  30.             contract.ReportProgress(progressPercent);
  31.         }
  32.     }
复制代码
 
宿主项目中需要实现HostView里HostObject抽象类。
  1. private class AutomationHost : HostView.HostObject
  2.         {
  3.             private ProgressBar progressBar;
  4.             public AutomationHost(ProgressBar progressBar)
  5.             {
  6.                 this.progressBar = progressBar;
  7.             }
  8.             public override void ReportProgress(int progressPercent)
  9.             {
  10.                 // Update the UI on the UI thread.
  11.                 progressBar.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
  12.                     (ThreadStart)delegate()
  13.                 {
  14.                     progressBar.Value = progressPercent;
  15.                 }
  16.                 );               
  17.             }
  18.         }
复制代码
 
然后是在宿主项目里激活插件,并初始化AutomationHost。
  1. string path = Environment.CurrentDirectory;            
  2. AddInStore.Update(path);//更新目录中Addins目录里的插件
  3. IList<AddInToken> tokens = AddInStore.FindAddIns(typeof(HostView.ImageProcessorHostView), path);//查找全部插件
  4. lstAddIns.ItemsSource = tokens;//插件可视化
  5. AddInToken token = (AddInToken)lstAddIns.SelectedItem;//选择插件
  6. AddInProcess addInProcess = new AddInProcess();//创建插件进程
  7. addInProcess.Start();//激活插件进程
  8. addin = token.Activate<HostView.ImageProcessorHostView>(addInProcess,AddInSecurityLevel.Internet);//激活插件
  9. //如果只是想隔离程序域,就无需创建AddInProcess,激活插件如下
  10. // HostView.ImageProcessorHostView addin = token.Activate<HostView.ImageProcessorHostView>(AddInSecurityLevel.Host);
  11. automationHost = new AutomationHost(progressBar);//创建AutomationHost类
  12. addin.Initialize(automationHost);//初始化automationHost
复制代码
 
 插件项目中实现AddInView中的抽象类。
  1. [AddIn("Negative Image Processor", Version = "1.0", Publisher = "Imaginomics",Description = "")]
  2.     public class NegativeImageProcessor : AddInView.ImageProcessorAddInView
  3.     {
  4.         public override byte[] ProcessImageBytes(byte[] pixels)
  5.         {
  6.             int iteration = pixels.Length / 100;         
  7.             for (int i = 0; i < pixels.Length - 2; i++)
  8.             {
  9.                 pixels[i] = (byte)(255 - pixels[i]);
  10.                 pixels[i + 1] = (byte)(255 - pixels[i + 1]);
  11.                 pixels[i + 2] = (byte)(255 - pixels[i + 2]);
  12.                 if (i % iteration == 0)
  13.                 {
  14.                     host?.ReportProgress(i / iteration);
  15.                 }
  16.             }
  17.             return pixels;
  18.         }
  19.         private AddInView.HostObject host;
  20.         public override void Initialize(AddInView.HostObject hostObj)
  21.         {
  22.             host = hostObj;
  23.         }
复制代码
 
 这时宿主可以把数据传递给插件程序,插件程序中ProcessImageBytes处理数据然后通过host?.ReportProgress(i / iteration);向宿主传递消息。
这里有提供样例程序。
项目附件.7z
4. MAF框架常见问题

4.1手动关闭插件
  1. AddInController addInController = AddInController.GetAddInController(addIn);
  2. addInController.Shutdown();
复制代码
此方法适应于非应用隔离的手动关闭。对于应用隔离式插件,用此方法会抛出异常。
如上面样例就是应用隔离的插件,可以根据进程id直接关闭进程。
  1. public void ProcessClose()
  2.         {
  3.             try
  4.             {
  5.                 if (process != null)
  6.                 {
  7.                     Process processes = Process.GetProcessById(addInProcess.ProcessId);
  8.                     if (processes?.Id > 0)
  9.                     {
  10.                         processes.Close();
  11.                     }
  12.                 }
  13.             }
  14.             catch (Exception)
  15.             {
  16.             }
  17.         }
复制代码
4.2 插件异常

System.Runtime.Remoting.RemotingException: 从 IPC 端口读取时失败: 管道已结束。这是插件最常见的异常,因为插件抛出异常而使得插件程序关闭。
如果是插件调用非托管代码,而产生的异常,可以查Windows应用程序日志来确定异常。其余能捕获的异常尽量捕获保存到日志,方便查看。
4.3 双向通信

实际应用过程中,往往是通过委托来将宿主相关函数暴露給一个类,然后通过在宿主程序初始化后。在插件中实例化后就可以直接调用宿主的相关函数,反之同理。
这里是通过委托暴露宿主的一个函数。
  1. public delegate void UpdateCallBack(string message, bool isclose, int leve);
  2.     public class VideoHost : HostAddInView
  3.     {
  4.         public event UpdateCallBack Updatecallback;
  5.         public override void ProcessVideoCallBack(string message, bool isclose, int leve)
  6.         {
  7.             Updatecallback?.Invoke(message, isclose, leve);
  8.         }
  9.     }
复制代码
 在插件程序中实例化后调用。
  1. private HostAddInView hostAddInView;
  2.         public override void Initialize(HostAddInView hostAddInView)
  3.         {
  4.             this.hostAddInView = hostAddInView;
  5.         }
  6.         private void ErrorCallback(string message, bool isclose, int leve)
  7.         {
  8.             hostAddInView?.ProcessVideoCallBack(message, isclose, leve);
  9.         }
复制代码
5. MAF深入理解

 MAF本质是实现IpcChannel通信,在一个期刊中有作者抛弃MAF固定结构自己实现IpcChannel,因为代码很复杂,就不在此详细阐述。

如果要实现应用域隔离,自己实现IpcChannel,MAF中的应用域隔离实现也是非常好的参考资料。

MAF的7层结构主要是实现从插件的宿主函数转换,例如可以在将插件程序的界面放入主界面中渲染,做出像浏览器一样的开一个界面就是一个进程。将插件中的组件在AddInSideAdapter中转换为Stream然后在HostSideAdapter中将Stream实例化为组件。而HostView和AddInView实际上是提供两个转换接口,Contract是定义传输接口。
另外如果传输插件向数组传递图像数据,最后是转换成byte[],或者使用共享内存。
如果有什么遗漏和错误,欢迎指正,批评。

来源:https://www.cnblogs.com/mrf2233/archive/2023/05/26/17434368.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具