[C#]插件编程框架 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[],或者使用共享内存。
如果有什么遗漏和错误,欢迎指正,批评。
[C#]插件编程框架 MAF 开发总结的更多相关文章
- .Net插件编程模型:MEF和MAF[转载]
.Net插件编程模型:MEF和MAF MEF和MAF都是C#下的插件编程框架,我们通过它们只需简单的配置下源代码就能轻松的实现插件编程概念,设计出可扩展的程序.这真是件美妙的事情! 今天抽了一点时间, ...
- 分享在winform下实现模块化插件编程
其实很早之前我就已经了解了在winform下实现插件编程,原理很简单,主要实现思路就是:先定一个插件接口作为插件样式及功能的约定,然后具体的插件就去实现这个插件接口,最后宿主(应用程序本身)就利用反射 ...
- jQuery 插件编程精讲与技巧
适应的读者: 1.有一定的jquery编程基础但是想在技能上有所提升的人 2.前端开发的程序员 3.对编程感兴趣的学生 为什么要学习jquery插件的编写? 为什么要学习jquery插件的编写?相信这 ...
- 使用 CodeIgniter 框架快速开发 PHP 应用(三)
原文:使用 CodeIgniter 框架快速开发 PHP 应用(三) 分析网站结构既然我们已经安装 CI ,我们开始了解它如何工作.读者已经知道 CI 实现了MVC式样. 通过对目录和文件的内容进行分 ...
- 初探Delphi中的插件编程
前言 我写Delphi程序是从MIS系统入门的,开始尝试子系统划分的时候采用的是MDI窗体的结构.随着系统功能的扩充,不断有新的子系统加入系统中,单个工程会变得非常大,每次做一点修改都要重新编译,单个 ...
- Python 四大主流 Web 编程框架
Python 四大主流 Web 编程框架 目前Python的网络编程框架已经多达几十个,逐个学习它们显然不现实.但这些框架在系统架构和运行环境中有很多共通之处,本文带领读者学习基于Python网络框架 ...
- Java EE互联网轻量级框架整合开发— SSM框架(中文版带书签)、原书代码
Java EE互联网轻量级框架整合开发 第1部分 入门和技术基础 第1章 认识SSM框架和Redis 2 1.1 Spring框架 2 1.2 MyBatis简介 6 1.3 Spring MVC简介 ...
- 分享在winform下实现模块化插件编程-优化版
上一篇<分享在winform下实现模块化插件编程>已经实现了模块化编程,但我认为不够完美,存在以下几个问题: 1.IAppContext中的CreatePlugInForm方法只能依据完整 ...
- 推荐25款实用的 HTML5 前端框架和开发工具【下篇】
快速,安全,响应式,互动和美丽,这些优点吸引更多的 Web 开发人员使用 HTML5.HTML5 有许多新的特性功能,允许开发人员和设计师创建应用程序和网站,带给用户桌面应用程序的速度,性能和体验. ...
- Navi.Soft30.框架.WinForm.开发手册
阅读导航 Navi.Soft30.Core类库.开发手册 http://www.cnblogs.com/xiyang1011/p/5709489.html Navi.Soft30.框架.WinForm ...
随机推荐
- Codeforces Round #648 (Div. 2) A~F题解
开始补cf了,还是记录一下,加深思路,打的应该都是div2.题面不截图了,直接说题意,思路,代码. A 题意:给一个01矩阵,两个人轮流填格子,仅当第i行,第j列全为0时才能填,不能填的人输,问谁赢? ...
- 除select外查询数据的另一种姿势
1.24 1.[GYCTF2020]Blacklist buuctf上的题目 1.解题过程 输入1会返回一个数组,加上单引号就报错了,说明存在注入 以前做过类似的估计是堆叠注入,尝试一下 注入成功 正 ...
- 使用 Agora 为Android APP添加视频直播
add-live-streaming-to-your-android-app-using-agora-featured1024×512 121 KB 视频互动直播是当前比较热门的玩法,我们经常见到有P ...
- 深入消息队列MQ,看这篇就够了!
大厂面试爱问消息队列 MQ. 因为消息队列MQ,既是大型分布式系统不可缺少的中间件,也是高并发系统的基石中间件. 如果你想要快速掌握消息队列 MQ 最内核的知识,以及消息队列MQ的主流应用场景.主流产 ...
- 算法总结--ST表
声明(叠甲):鄙人水平有限,本文为作者的学习总结,仅供参考. 1. RMQ 介绍 在开始介绍 ST 表前,我们先了解以下它以用的场景RMQ问题.RMQ (Range Minimum/Maximum Q ...
- 智能且集成的端到端移动应用程序安全解决方案——Quixxi简介
移动应用程序安全变得简单快捷 Quixxi 是一种智能且集成的端到端移动应用程序安全解决方案.这个强大的工具可供开发人员在几分钟内保护和监控任何移动应用程序. Quixxi Security 评估应用 ...
- [Windows/Linux]Linux下的正斜杠"/"和"\"的区别 [转载]
执行某一条Linux命令时,遇到了此问题,甚为不解.[文由] 本篇属于全文转载自: Linux下的正斜杠"/"和""的区别 - 博客园 >>> ...
- 单元测试、文档测试、读写文件、StringIO和BytesIO
1.单元测试就是为了测试程序执行的正确性 2.编写单元测试时,需要编写一个单元测试类,继承unittest TestCase类 3.单元测试最常用的断言是assert,断言期望抛出指定的异常 4.运行 ...
- GitHub Pulse 是什么?它是否能衡量 OpenTiny 开源项目的健康程度?
Pulse 是"脉搏"的意思,就像一个人要有脉搏才能算是一个活人,一个开源项目要有"脉搏"才能算是一个"活"的开源项目,这个单词非常形象地表 ...
- 手机号码归属地 API 实现个性化推荐的思路分析
前言 随着移动互联网和智能手机的普及,越来越多的人使用手机上网和购物,移动营销已成为企业获取用户和提升品牌知名度的重要手段.手机号码归属地 API 作为移动营销的关键工具,具有广阔的应用前景. 本文将 ...