惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Help Net Security
博客园 - Franky
GbyAI
GbyAI
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
爱范儿
爱范儿
IT之家
IT之家
酷 壳 – CoolShell
酷 壳 – CoolShell
aimingoo的专栏
aimingoo的专栏
博客园_首页
MongoDB | Blog
MongoDB | Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Recent Announcements
Recent Announcements
Scott Helme
Scott Helme
有赞技术团队
有赞技术团队
M
MIT News - Artificial intelligence
C
CERT Recently Published Vulnerability Notes
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Jina AI
Jina AI
F
Fortinet All Blogs
N
Netflix TechBlog - Medium
L
LangChain Blog
L
LINUX DO - 最新话题
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
H
Hacker News: Front Page
MyScale Blog
MyScale Blog
P
Palo Alto Networks Blog
G
Google Developers Blog
Google DeepMind News
Google DeepMind News
AI
AI
T
Troy Hunt's Blog
Microsoft Azure Blog
Microsoft Azure Blog
阮一峰的网络日志
阮一峰的网络日志
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Vercel News
Vercel News
Microsoft Security Blog
Microsoft Security Blog
罗磊的独立博客
S
Secure Thoughts
大猫的无限游戏
大猫的无限游戏
博客园 - 叶小钗
人人都是产品经理
人人都是产品经理
Blog — PlanetScale
Blog — PlanetScale
博客园 - 司徒正美
Apple Machine Learning Research
Apple Machine Learning Research
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 三生石上(FineUI控件)
S
Security @ Cisco Blogs
Cloudbric
Cloudbric
E
Exploit-DB.com RSS Feed
Attack and Defense Labs
Attack and Defense Labs

博客园 - 妖居

ASP.NET MVC Tips #2 - 令人混乱的Get、Post、Return View和Return Redirect ASP.NET MVC Tips #1 - 支持上传文件的ModelBinder How to migrate MsSql database to MySql Windows Workflow Foundation 使用小例 使用异步委托解决Windows Application应用Duplex Service时出现的Deadlock问题 字节数组、数值和十六进制字符串的转换 表格化固定长、CSV文件编辑器工具 iMatrixitor 发布 Getting Started With LINQ in Visual Basic (翻译 + 评论) 两个简单方法加速DataGridView 使用.NET自带的功能制作简单的注册码 不是说“Peek 不会更改 StreamReader 的当前位置”么。MS骗人的! 《Introducing Visual Basic 2005》中看到的一些VB2005的新特性 VB.NET函数的返回值问题(从CSDN论坛一个问题想到的) Add-in and Automation Development In VB.NET 2003 (Finished) Add-in and Automation Development In VB.NET 2003 (8) 模拟IE地址栏的TextBox小控件 Add-in and Automation Development in VB.NET 2003 (6-7) 在WinXP环境下显示XP风格的控件 Add-in and Automation Development In VB.NET 2003 (5)
使用接口实现附带插件功能的程序
妖居 · 2007-01-08 · via 博客园 - 妖居

插件功能给软件的使用者可以扩充软件功能的机会。我们不可能让软件适用于所有人,也不是所有的人都会出资帮助你实现他们的需求。插件功能提供了一个软件的高度可扩充性,允许用户作为软件的二次开发者,继续完善软件的功能。

为了在软件中加入插件功能,我们需要下面几个特别的条件:

(1)      本软件(此后我们称之为‘宿主程序’)需要开放自己的成员,包括属性、方法、事件为插件程序提供服务。

(2)      宿主程序要很好的隐藏一些信息,阻止插件程序有意或无意的破坏本身的功能。

(3)      宿主程序提供插件服务,方便插件功能的升级。

(4)      定义一个统一的标志信息,保证宿主程序可以正常的识别并运行插件程序而不会出现类型安全问题。

为此我们模仿Visual Studio .Net本身提供的Addin的实现机制来实现我们的插件程序。

第一步,制作接口。它是建立在宿主程序和插件之间的桥梁。

首先我们建立一个“插件标识”接口,这个接口用来标识我们的插件类的特性。

Public Interface IPlugins

    Sub Connect(ByVal PluginsApp As IPluginsApplication)

End Interface

这个接口里面我们定义了一个方法Connect,用来启动我们的插件程序。也就是说,我们的宿主程序将会统一使用Connect方法启动插件程序。而对于插件程序,入口地址将是Connect方法。这个有点类似于普通应用程序的Main函数。Connect函数的参数IPluginsApplication表示我们宿主程序的实例,稍后将会进一步解释。

其次,我们建立一个“插件服务”接口,这个接口将宿主程序需要开放的属性、方法、实现定义出来,通过接口的方式提供给插件程序。

Public Interface IPluginsApplication

    Event Display(ByVal sender As Object, ByVal e As EventArgs)

    Property Caption() As String

    Sub DisplayInput(ByVal Text As String)

End Interface

在例子中我们简单定义了一个事件、一个属性和一个方法。插件程序在开发的时候只要引用了我们的“插件服务”接口就可以调用里面定义的内容了。

第二步,建立宿主程序。

但是光有接口是不能执行里面的内容的,必须要有一个实现了这个接口的实例才可以。这里我们让宿主程序实现这个接口,并且实现这些接口里面的内容,让插件程序可以进行操作。

Public Class Form1

    Implements PluginsInterface.IPluginsApplication

    Public Event Display(ByVal sender As Object, ByVal e As System.EventArgs) Implements PluginsInterface.IPluginsApplication.Display

    Public Property Caption() As String Implements PluginsInterface.IPluginsApplication.Caption

        Get

            Return Me.Text

        End Get

        Set(ByVal value As String)

            Me.Text = value

        End Set

    End Property

    Public Sub DisplayInput(ByVal Text As String) Implements PluginsInterface.IPluginsApplication.DisplayInput

        MsgBox("输入内容:" & Text)

    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        RaiseEvent Display(Me, New EventArgs)

        Me.DisplayInput(Me.TextBox1.Text)

    End Sub

End Class

我们用一个标准的Windows窗体来实现“插件服务”接口的内容。通过Caption属性可以更改窗体的标题,通过DisplayInput方法可以显示字符串。

这样我们的宿主程序由于实现了“插件服务”接口,就可以通过“插件服务”接口来将实例传递进插件程序。我们回到“插件标识”接口的Connect方法,插件的入口函数参数将宿主程序通过“插件服务”接口的实例传递到插件程序内,使得插件程序可以调用宿主程序开放的内容。而且由于通过接口传递,这些操作都是类型安全的。

第三步,寻找并启动插件。

插件程序将会以动态链接库的形式提供,这就需要我们的宿主程序找到插件程序的文件,判断是不是合法的插件,实例化并且启动。

首先我们必须定义一个插件存放的路径,比如运行目录下面的Plugins目录。然后寻找这一目录下面所有的dll文件进行判断。限于篇幅的原因,我们这里使用绝对路径来代替。

对于找到的文件,通过反射我们就可以得到定义于这个dll文件中的所有类定义信息。通过刚才我们说的“插件标识”接口逐个判断,将实现了“插件标识”接口的类作为我们判断合法的插件类。然后使用实例化方法进行实例化。(注意,我们的插件程序默认一个无参数的实例化方法。)通过强制类型转换,将这个Object的实例转化为我们的“插件标识”接口实例,也就是IPlugins。由于此前我们已经判断过了,这个类实现了“插件标识”接口(也就是IPlugins接口),所以这个转换是安全的。最后通过IPluginsConnect方法启动接口程序,将宿主程序,也就是我们的窗体实例通过参数传递。(由于我们的窗体已经实现了接口IPluginsApplication,所以这步操作也是安全的。)此后,程序将由插件接管,对于宿主程序,插件和宿主自己同时进行操作。

    Dim pobj As PluginsInterface.IPlugins

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click

        Dim ass As Reflection.Assembly = Reflection.Assembly.LoadFile("E:\Visual Sutdio Project 2005\PluginsApplication\Plugins\bin\Debug\Plugins.dll")

        Dim t As Type = Nothing

        For Each t In ass.GetTypes

            If t.IsClass AndAlso t.GetInterface(GetType(PluginsInterface.IPlugins).FullName, True) IsNot Nothing Then

                Exit For

            End If

        Next

        If t IsNot Nothing Then

            pobj = ass.CreateInstance(t.FullName, True)

            pobj.Connect(Me)

        End If

    End Sub

第四步,制作插件。

将一个类实现“插件标识”接口,用来表示这个类是宿主程序可识别的插件。同时必须实现Connect方法。通过Connect的参数,插件程序可以使用宿主程序通过“插件服务”接口提供的功能。

Public Class Plugins1

    Implements PluginsInterface.IPlugins

    Private WithEvents m_papp As PluginsInterface.IPluginsApplication

    Public Sub Connect(ByVal PluginsApp As PluginsInterface.IPluginsApplication) Implements PluginsInterface.IPlugins.Connect

        MsgBox("插件启动成功。")

        Me.m_papp = PluginsApp

        Me.m_papp.Caption = InputBox("请输入宿主程序的窗体标题")

        Me.m_papp.DisplayInput(InputBox("请输入字符串"))

    End Sub

    Private Sub m_papp_Display(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_papp.Display

        Dim f As New Form1

        f.ShowDialog()

    End Sub

End Class

通过上述方法,我们就制作完成了一个简单的插件。

总结一下:

(1)      通过接口定义插件的标识,进行类型验证并启动插件程序。这样做的好处是统一了插件的类型并且可以安全的进行启动。

(2)      通过接口定义宿主程序希望公开的功能。这样做一方面保证了宿主程序不会被插件程序完全的控制,另一方面让插件程序可以安全的运行宿主提供的方法。缺点是宿主程序如果有多层嵌套的类关系需要开放的话,需要将所有的类都重新通过接口进行封装。

(3)      宿主程序、插件程序引用统一的接口程序,将插件的开发了宿主程序本身脱离,提高宿主的安全性,并且防止了循环引用的发生。

 PS:本文所用到的程序代码由此下载