概念:

插件(add-in、plug-in)是应用程序能够动态发现、加载和使用的单独编译过的组件。

  • 优点:

允许第三方开发人员扩展应用程序的功能。如PS中的插件提供大量图片处理效果; firefox中插件提供了增强Web冲浪及全新功能。插件模型的主要优点是不需要为许多任务(如发现)编写底层代码,主要缺点是非常复杂。

内容:

MAF:托管插件框架下的插件模型。较可靠的框架,适用于应用程序和插件不同团队各自开发,还特别适于第三方插件;
MAF依赖于定义的接口,在处理不同版本、允许将插件加载到独立应用程序域中,有很大灵活优势。缺陷是为支持这些功能,MAF显得复杂,设置繁琐。
MEF:托管可扩展性框架的新模型。轻量级选择,适用于单个开发团队,用于以不同方式组装模块化程序,为单独的发布提供不同的功能实现。
缺陷是太松散,相互关联的部件一复杂就容易变得混乱。详情查看http://tinyurl.com/37s2jdx
如对可组合的应用程序有兴趣,可查看复合应用程序库CAL(CAL只针对WPF应用程序)。而MEF是用于构建各种模块化.NET应用程序的通用解决方案。以下是MAF内容示例。

1 了解插件管道


clipboard.png

2 管道的工作原理

clipboard.png

clipboard.png

3 插件文件夹结构

ps:a,当进行编译时,output目录通常放置应用程序和所有管道组件的地方。
b,修改好每个组件项目的生成路径,及防止复制引用的程序集,设置copy local为false。
clipboard.png

4 分析使用过程:

首先,宿主应用程序调用宿主视图中的方法。背后体现为应用程序通过宿主视图调用宿主方适配器中的方法,
然后宿主方适配器调用协定接口的相应方法,该方法是由插件方适配器实现的。
最后,插件方适配器调用插件视图中的方法。这个方法是由插件实现的,负责执行实际工作。

5 程序代码及介绍

效果图

clipboard.png
clipboard.png

  • 协定:
using System.AddIn.Pipeline;
using System.AddIn.Contract;

namespace Contract
{
    [AddInContract]
    public interface IImageProcessorContract : IContract
    {
        byte[] ProcessImageBytes(byte[] pixels);
    }
}

ps:可在协定程序集中自定义传递类型,可串行化的。或者设计接口提供一个返回一系列可配置参数的方法。

  • 插件视图
namespace AddInView
{    
    [AddInBase]
    public abstract class ImageProcessorAddInView
    {
        public abstract byte[] ProcessImageBytes(byte[] pixels);
    }
}
  • 插件
using System;
using System.AddIn;

namespace FadeImageAddIn
{
    [AddIn("Fade Image Processor", Version = "1.0.0.0", Publisher = "SupraImage",
            Description = "Darkens the picture")]
    public class FadeImageProcessor : AddInView.ImageProcessorAddInView
    {
        public override byte[] ProcessImageBytes(byte[] pixels)
        {
            Random rand = new Random();
            int offset = rand.Next(0, 10);
            for (int i = 0; i < pixels.Length - 1 - offset; i++)
            {
                if ((i + offset) % 5 == 0)
                {
                    pixels[i] = 0;
                }
            }
            return pixels;
        }
    }
}
  • 插件适配器

ps:插件适配器必须提供接收恰当视图类的实例作为参数的构造函数,以备后用。

using System.AddIn.Pipeline;

namespace AddInSideAdapter
{
    [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);
        }
    }
}
  • 宿主视图
namespace HostView
{
    public abstract class ImageProcessorHostView
    {
        public abstract byte[] ProcessImageBytes(byte[] pixels);
    }
}

e,宿主适配器
接收一个实现了协定的对象,然后调用宿主方适配器的方法使用该对象。然后后台调用协定接口方法,向前穿过应用程序边界,并转换为调用插件适配器的相应方法。

using System.AddIn.Pipeline;

namespace HostSideAdapter
{
    [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);
        }
    }
}
  • 宿主

现在已构建好了插件模型的基础架构,最后是创建使用插件模型的应用程序,任何类型的.net可执行程序都可以作为宿主,但当前为WPF宿主。

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using System.AddIn.Hosting;

namespace ApplicationHost
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {                     
            string path = Environment.CurrentDirectory;            
            AddInStore.Update(path);

            IList<AddInToken> tokens = AddInStore.FindAddIns(typeof(HostView.ImageProcessorHostView), path);            
            lstAddIns.ItemsSource = tokens;        
        }

        private void cmdProcessImage_Click(object sender, RoutedEventArgs e)
        {            
            BitmapSource originalSource = (BitmapSource)img.Source;
            int stride = originalSource.PixelWidth * originalSource.Format.BitsPerPixel/8;
            stride = stride + (stride % 4) * 4;
            byte[] originalPixels = new byte[stride * originalSource.PixelHeight * originalSource.Format.BitsPerPixel / 8];
            
            originalSource.CopyPixels(originalPixels, stride, 0);
            
            AddInToken token = (AddInToken)lstAddIns.SelectedItem;
            HostView.ImageProcessorHostView addin = token.Activate<HostView.ImageProcessorHostView>(AddInSecurityLevel.Internet);
            byte[] changedPixels = addin.ProcessImageBytes(originalPixels);
            
            BitmapSource newSource = BitmapSource.Create(originalSource.PixelWidth, originalSource.PixelHeight, originalSource.DpiX, originalSource.DpiY, originalSource.Format, originalSource.Palette, changedPixels, stride);
            img.Source = newSource;
        }

        private void lstAddIns_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            cmdProcessImage.IsEnabled = (lstAddIns.SelectedIndex != -1);
        }
    }
}
<Window x:Class="ApplicationHost.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="ApplicationHost" Height="300" Width="300" Loaded="Window_Loaded">
    <Grid Margin="3">
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition Height="2*"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <ListBox Name="lstAddIns" Margin="3" SelectionChanged="lstAddIns_SelectionChanged">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Margin="3,3,0,8" HorizontalAlignment="Stretch">
                        <TextBlock Text="{Binding Path=Name}" FontWeight="Bold" ></TextBlock>
                        <TextBlock Text="{Binding Path=Publisher}" ></TextBlock>
                        <TextBlock Text="{Binding Path=Description}" FontSize="10" FontStyle="Italic"></TextBlock>                        
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button Grid.Column="1" Name="cmdProcessImage" Click="cmdProcessImage_Click" Margin="0,3,3,3" Padding="3" VerticalAlignment="Top" IsEnabled="False">Go</Button>
        <Image Grid.Row="1" Grid.ColumnSpan="2" Name="img" Source="Forest.jpg" Margin="3" />
    </Grid>
</Window>

ps:当调用AddInToken.Actiovate<T>方法时,在后台需要执行较多步骤:
(1)为插件创建新的应用程序域。
(2)插件程序集被加载到新的应用程序域。
(3)在新的应用程序域中实例化插件适配器。
(4)(通过远程代理)使得宿主额应用程序域中科院获得插件适配器。
(5)在苏州应用程序域中实例化宿主适配器。
(6)将宿主适配器返回到宿主应用程序(作为宿主视图类型)

另外:可使用相同的插件创建任意数量的不同插件。该例子有两个插件,以不同方式处理图片。

以上《WPF编程宝典》示例。


李志玮
22 声望34 粉丝

求索~~