#title(.NETプログラミング研究 第40号)

#contents

*.NETプログラミング研究 第40号 [#ucb8d008]

**.NET Tips [#k1b62165]

**プラグイン機能を持つアプリケーションを作成する - その2 [#u9b56728]

#column(注意){{
この記事の最新版は「[[プラグイン機能を持つアプリケーションを作成する>http://dobon.net/vb/dotnet/programing/plugin.html]]」で公開しています。
}}

(お詫び)今回はコードがかなり長くなってしまいました。ご了承ください。

前回の「プラグイン機能を持つアプリケーションを作成する - その1」ではプラグイン機能を実現させるための基本的な考え方について説明しました。簡単に復習すると、その方法とは、インターフェイスを使用するというものでした。詳細は前回のメールマガジンでご確認ください。

-[[.NETプログラミング研究 第39号>../39]]

今回はさらに実用的な例として、Windowsアプリケーションでプラグイン機能を実現する方法を考えます。ここでは具体的に、プラグインの使用できる簡単なエディタを作成します。

あくまでプラグイン機能を説明することが目的ですので、エディタは思い切り単純にし、フォームにRichTextBoxとMainMenuのみを配置することにします。プラグインからエディタのRichTextBoxコントロールにアクセスすることにより、プラグインの機能が果たせるようにします。

***インターフェイスを定義する [#m998a78d]

前回と同じように、まずプラグインのクラスが実装すべきインターフェイスを定義します。今回はプラグインのインターフェイスに加えて、プラグインを使用するホストの側が実装すべきインターフェイスも定義することにします。ホストのためのインターフェイスでは、プラグインのホストとして必要な機能をメンバとして定義し、プラグインからこのメンバを通してホストにアクセスできるようにします。

具体的には、プラグインから指定されたメッセージをホストで表示するためのメソッドと、ホストのRichTextBoxコントロールを取得するためのプロパティ、さらにホストのメインフォームを取得するためのプロパティを定義することにします。

それでは実際にこれらのインターフェイスを作成してみましょう。前号と同様、クラスライブラリとして作成するため、Visual Studio .NETではクラスライブラリのプロジェクトを作成し("Plugin"という名前で作成しています)、.NET SDKでは/target:libraryコンパイラオプションを使用します。また、アセンブリファイル名は、"Plugin.dll"とします。さらに今回はWindowsアプリケーションを扱うため、"System.Windows.Forms.dll"を参照に追加します。(参照に追加するには、Visual Studio .NETの場合は、ソリューションエクスプローラの「参照設定」を、.NET SDKの場合は、/referenceコンパイラオプションを使用します。)

コードは、次のようになります。IPluginインターフェイスがプラグインのためのインターフェイスで、IPluginHostインターフェイスがプラグインのホストのためのインターフェイスです。

#code(vbnet){{
Imports System
Imports System.Windows.Forms

Namespace Plugin
    ''' <summary>
    ''' プラグインで実装するインターフェイス
    ''' </summary>
    Public Interface IPlugin
        ''' <summary>
        ''' プラグインの名前
        ''' </summary>
        ReadOnly Property Name() As String

        ''' <summary>
        ''' プラグインのバージョン
        ''' </summary>
        ReadOnly Property Version() As String

        ''' <summary>
        ''' プラグインの説明
        ''' </summary>
        ReadOnly Property Description() As String

        ''' <summary>
        ''' プラグインのホスト
        ''' </summary>
        Property Host() As IPluginHost

        ''' <summary>
        ''' プラグインを実行する
        ''' </summary>
        Sub Run()
    End Interface

    ''' <summary>
    ''' プラグインのホストで実装するインターフェイス
    ''' </summary>
    Public Interface IPluginHost
        ''' <summary>
        ''' ホストのメインフォーム
        ''' </summary>
        ReadOnly Property MainForm() As Form

        ''' <summary>
        ''' ホストのRichTextBoxコントロール
        ''' </summary>
        ReadOnly Property RichTextBox() As RichTextBox

        ''' <summary>
        ''' ホストでメッセージを表示する
        ''' </summary>
        ''' <param name="plugin">メソッドを呼び出すプラグイン</param>
        ''' <param name="msg">表示するメッセージ</param>
        Sub ShowMessage(ByVal plugin As IPlugin, ByVal msg As String)
    End Interface
End Namespace
}}

#code(csharp){{
using System;
using System.Windows.Forms;

namespace Plugin
{
	/// <summary>
	/// プラグインで実装するインターフェイス
	/// </summary>
	public interface IPlugin
	{
		/// <summary>
		/// プラグインの名前
		/// </summary>
		string Name {get;}

		/// <summary>
		/// プラグインのバージョン
		/// </summary>
		string Version {get;}

		/// <summary>
		/// プラグインの説明
		/// </summary>
		string Description {get;}

		/// <summary>
		/// プラグインのホスト
		/// </summary>
		IPluginHost Host {get; set;}

		/// <summary>
		/// プラグインを実行する
		/// </summary>
		void Run();
	}

	/// <summary>
	/// プラグインのホストで実装するインターフェイス
	/// </summary>
	public interface IPluginHost
	{
		/// <summary>
		/// ホストのメインフォーム
		/// </summary>
		Form MainForm {get;}

		/// <summary>
		/// ホストのRichTextBoxコントロール
		/// </summary>
		RichTextBox RichTextBox {get;}

		/// <summary>
		/// ホストでメッセージを表示する
		/// </summary>
		/// <param name="plugin">メソッドを呼び出すプラグイン</param>
		/// <param name="msg">表示するメッセージ</param>
		void ShowMessage(IPlugin plugin, string msg);
	}
}
}}

IPluginは前回と比べ、プラグインのバージョンと説明を取得するためのプロパティが新たに追加され、さらに、Runメソッドもパラメータ、返り値がなくなりました。また、IPluginHostを設定、取得するためのプロパティも加えられています。

***プラグインを作成する [#kd83490b]

次に、IPluginインターフェイスを実装したプラグインのクラスを作成します。ここでは、RichTextBox内の文字数を表示するプラグイン(CountCharsクラス)を作成してみましょう。

プラグインも前号と同様に、クラスライブラリとして作成し、"Plugin.dll"を参照に追加します。また、"System.Windows.Forms.dll"も参照に追加してください。出力するアセンブリファイル名は、"CountChars.dll"とします。(Visual Studio .NETのVB.NETの場合は、プロジェクトのプロパティの「ルート名前空間」が空白になっているものとします。デフォルトではプロジェクト名となっていますので、変更する必要があります。)

CountCharsクラスのコードは次のようになります。

#code(vbnet){{
Imports System

Namespace CountChars
    ''' <summary>
    ''' 文字数を表示するためのプラグイン
    ''' </summary>
    Public Class CountChars
        Implements Plugin.IPlugin

        Private _host As Plugin.IPluginHost

        'IPluginのメンバ
        Public ReadOnly Property Name() As String _
            Implements Plugin.IPlugin.Name
            Get
                Return "文字数取得"
            End Get
        End Property

        Public ReadOnly Property Version() As String _
            Implements Plugin.IPlugin.Version
            Get
                '自分自身のAssemblyを取得し、バージョンを返す
                Dim asm As System.Reflection.Assembly = _
                    System.Reflection.Assembly.GetExecutingAssembly()
                Dim ver As System.Version = asm.GetName().Version
                Return ver.ToString()
            End Get
        End Property

        Public ReadOnly Property Description() As String _
            Implements Plugin.IPlugin.Description
            Get
                Return "エディタで編集中の文章の文字数を表示します。"
            End Get
        End Property

        Public Property Host() As Plugin.IPluginHost _
            Implements Plugin.IPlugin.Host
            Get
                Return Me._host
            End Get
            Set(ByVal Value As Plugin.IPluginHost)
                Me._host = Value
            End Set
        End Property

        ''' <summary>
        ''' RichTextBoxの文字数を表示する
        ''' </summary>
        Public Sub Run() Implements Plugin.IPlugin.Run
            Dim msg As String = String.Format("文字数 : {0} 文字", _
                Me._host.RichTextBox.Text.Length)
            Me._host.ShowMessage(Me, msg)
        End Sub
    End Class
End Namespace
}}

#code(csharp){{
using System;

namespace CountChars
{
	/// <summary>
	/// 文字数を表示するためのプラグイン
	/// </summary>
	public class CountChars : Plugin.IPlugin
	{
		private Plugin.IPluginHost _host;

		//IPluginのメンバ
		public string Name
		{
			get
			{
				return "文字数取得";
			}
		}
		public string Version
		{
			get
			{
				//自分自身のAssemblyを取得し、バージョンを返す
				System.Reflection.Assembly asm = 
					System.Reflection.Assembly.GetExecutingAssembly();
				System.Version ver = asm.GetName().Version;
				return ver.ToString();
			}
		}
		public string Description
		{
			get
			{
				return "エディタで編集中の文章の文字数を表示します。";
			}
		}
		public Plugin.IPluginHost Host
		{
			get
			{
				return this._host;
			}
			set
			{
				this._host = value;
			}
		}
		/// <summary>
		/// RichTextBoxの文字数を表示する
		/// </summary>
		public void Run()
		{
			string msg = 
				string.Format("文字数 : {0} 文字", 
					this._host.RichTextBox.Text.Length);
			this._host.ShowMessage(this, msg);
		}
	}
}
}}

特に説明を必要とする箇所はないでしょう。Runメソッドでは、IPluginHostオブジェクトからRichTextBoxにアクセスし、文字数を取得し、IPluginHostのShowMessageメソッドで結果をホストで表示しています。

***ウィンドウを表示するプラグインの作成 [#r6228f91]

次にWindowsアプリケーションらしく、ウィンドウを表示するプラグインを作成してみましょう。ここでは、「検索」ウィンドウにより、RichTextBoxから指定された文字列を検索するプラグインを作ります(エディタとしては、プラグインで処理する機能ではありませんが)。

まず、CountCharsクラスと同様、クラスライブラリのプロジェクトを作成し(名前は、"FindString"とします)、"Plugin.dll"と"System.Windows.Forms.dll"を参照に追加します("System.Windows.Forms.dll"は今追加しなくても、「Windowsフォームの追加」でプロジェクトにフォームを追加すれば、自動的に追加されます)。

続いて、「Windowsフォームの追加」でプロジェクトにフォーム(FindForm)を追加し、「検索」ウィンドウを作成します。このフォームに配置するコントロール及び、変更するプロパティ(あるいはイベント)の一覧は次のようになります。


#pre{{
コントロール: Form
Name: FindForm
Text: 検索
AcceptButton: findButton
CancelButton: closeButton
FormBorderStyle: FixedToolWindow
ShowInTaskbar: false

コントロール: TextBox
Name: findString
Text: ""

コントロール: Button
Name: findButton
Text: 次を検索
DialogResult: OK
Clickイベント: findButton_Click

コントロール: Button
Name: closeButton
Text: 閉じる
DialogResult: Cancel
Clickイベント: closeButton_Click

コントロール: CheckBox
Name: wholeWord
Text: 単語単位で検索する

コントロール: CheckBox
Name: matchCase
Text: 大文字小文字を区別する

コントロール: CheckBox
Name: reverse
Text: 上へ検索する
}}

さらに、FindFormクラスに次のコードを追加し、指定されたRichTextBoxを検索できるようにします。

#code(vbnet){{
'Imports System.Windows.Forms
'がコードの先頭に書かれているものとする

'検索するRichTextBox
Friend RichTextBox As RichTextBox

Private Sub findButton_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles findButton.Click
    '検索オプションを決定する
    Dim finds As RichTextBoxFinds = RichTextBoxFinds.None
    If Me.wholeWord.Checked Then
        finds = finds Or RichTextBoxFinds.WholeWord
    End If
    If Me.matchCase.Checked Then
        finds = finds Or RichTextBoxFinds.MatchCase
    End If
    If Me.reverse.Checked Then
        finds = finds Or RichTextBoxFinds.Reverse
    End If

    '検索範囲を決定する
    Dim startPos, endPos As Integer
    If Not Me.reverse.Checked Then
        startPos = Me.RichTextBox.SelectionStart + _
            Me.RichTextBox.SelectionLength
        endPos = -1
        If startPos >= Me.RichTextBox.TextLength Then
            MessageBox.Show("検索が完了しました。", "検索")
            Return
        End If
    Else
        startPos = 0
        endPos = Me.RichTextBox.SelectionStart
        If endPos <= 0 Then
            MessageBox.Show("検索が完了しました。", "検索")
            Return
        End If
    End If

    '検索する
    If Me.RichTextBox.Find( _
        findString.Text, startPos, endPos, finds) < 0 Then
        MessageBox.Show("検索が完了しました。", "検索")
    Else
        Me.RichTextBox.Focus()
    End If
End Sub

Private Sub closeButton_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles closeButton.Click
    Me.Close()
End Sub
}}

#code(csharp){{
//using System.Windows.Forms;
//がコードの先頭に書かれているものとする

//検索するRichTextBox
internal RichTextBox RichTextBox;

//「次を検索」がクリックされた時
private void findButton_Click(
	object sender, System.EventArgs e)
{
	//検索オプションを決定する
	RichTextBoxFinds finds = RichTextBoxFinds.None;
	if (this.wholeWord.Checked)
		finds |= RichTextBoxFinds.WholeWord;
	if (this.matchCase.Checked)
		finds |= RichTextBoxFinds.MatchCase;
	if (this.reverse.Checked)
		finds |= RichTextBoxFinds.Reverse;

	//検索範囲を決定する
	int startPos, endPos;
	if (!this.reverse.Checked)
	{
		startPos = this.RichTextBox.SelectionStart + 
			this.RichTextBox.SelectionLength;
		endPos = -1;
		if (startPos >= this.RichTextBox.TextLength)
		{
			MessageBox.Show("検索が完了しました。", "検索");
			return;
		}
	}
	else
	{
		startPos = 0;
		endPos = this.RichTextBox.SelectionStart;
		if (endPos <= 0)
		{
			MessageBox.Show("検索が完了しました。", "検索");
			return;
		}
	}

	//検索する
	if (this.RichTextBox.Find(
		findString.Text, startPos, endPos, finds) < 0)
		MessageBox.Show("検索が完了しました。", "検索");
	else
		this.RichTextBox.Focus();
}

//「閉じる」がクリックされた時
private void closeButton_Click(
	object sender, System.EventArgs e)
{
	this.Close();
}
}}

次にこのフォームを表示させるプラグインクラス(FindString)を作成します。コードは、次のようになります。

#code(vbnet){{
Imports System
Imports System.Windows.Forms

Namespace FindString
    Public Class FindString
        Implements Plugin.IPlugin

        Private _host As Plugin.IPluginHost
        Private _mainForm As FindForm

        'IPluginのメンバ
        Public ReadOnly Property Name() As String _
            Implements Plugin.IPlugin.Name
            Get
                Return "文字列の検索"
            End Get
        End Property

        Public ReadOnly Property Description() As String _
            Implements Plugin.IPlugin.Description
            Get
                Return "編集中の文章から指定された文字列を検索します。"
            End Get
        End Property

        Public Property Host() As Plugin.IPluginHost _
            Implements Plugin.IPlugin.Host
            Get
                Return _host
            End Get
            Set(ByVal Value As Plugin.IPluginHost)
                _host = Value
            End Set
        End Property

        Public ReadOnly Property Version() As String _
            Implements Plugin.IPlugin.Version
            Get
                '自分自身のAssemblyを取得し、バージョンを返す
                Dim asm As System.Reflection.Assembly = _
                    System.Reflection.Assembly.GetExecutingAssembly()
                Dim ver As System.Version = asm.GetName().Version
                Return ver.ToString()
            End Get
        End Property

        Public Sub Run() Implements Plugin.IPlugin.Run
            'フォームが表示されていれば、アクティブにして終了
            If Not (Me._mainForm Is Nothing) AndAlso _
                Not Me._mainForm.IsDisposed Then
                Me._mainForm.Activate()
                Return
            End If

            '検索ウィンドウを作成し、表示する
            Me._mainForm = New FindForm
            Me._mainForm.RichTextBox = Me._host.RichTextBox
            Me._mainForm.Owner = Me._host.MainForm
            Me._mainForm.Show()
        End Sub
    End Class
End Namespace
}}

#code(csharp){{
using System;
using System.Windows.Forms;

namespace FindString
{
	public class FindString : Plugin.IPlugin
	{
		private Plugin.IPluginHost _host;
		private FindForm _mainForm;

		//IPluginのメンバ
		public string Name
		{
			get
			{
				return "文字列の検索";
			}
		}
		public string Description
		{
			get
			{
				return "編集中の文章から指定された文字列を検索します。";
			}
		}
		public Plugin.IPluginHost Host
		{
			get
			{
				return _host;
			}
			set
			{
				_host = value;
			}
		}
		public string Version
		{
			get
			{
				//自分自身のAssemblyを取得し、バージョンを返す
				System.Reflection.Assembly asm = 
					System.Reflection.Assembly.GetExecutingAssembly();
				System.Version ver = asm.GetName().Version;
				return ver.ToString();
			}
		}

		public void Run()
		{
			//フォームが表示されていれば、アクティブにして終了
			if (this._mainForm != null && !this._mainForm.IsDisposed)
			{
				this._mainForm.Activate();
				return;
			}

			//検索ウィンドウを作成し、表示する
			this._mainForm = new FindForm();
			this._mainForm.RichTextBox = this._host.RichTextBox;
			this._mainForm.Owner = this._host.MainForm;
			this._mainForm.Show();
		}
	}
}
}}

RunメソッドでFindFormを表示しているだけで、特に問題はないでしょう。(表示する前にRichTextBoxの設定と、オーナーウィンドウの設定を行っています。)

***メインアプリケーションの作成 [#v0d4f190]

ようやくここまでたどり着きました。いよいよプラグインを使用するホストのアプリケーションを作成します。

ホストアプリケーションは、Windowsアプリケーションプロジェクトとして作成し(名前は、"MainApplication"とします)、"Plugin.dll"を参照に追加します。

フォームには、RichTextBoxと、メニュー、さらにメッセージを表示するためのStatusBarコントロールを配置します。変更するフォームのプロパティと、フォームに配置するコントロールとそのプロパティ(そしてイベント)の一覧を以下に示します。

#pre{{
コントロール: Form
Name: Form1
Menu: mainMenu
Loadイベント: Form1_Load

コントロール: RichTextBox
Name: mainRichTextBox
Dock: Fill

コントロール: StatusBar
Name: mainStatusbar

コントロール: MainMenu
Name: mainMenu

コントロール: MenuItem
Name: menuPlugins
Text: プラグイン(&P)

コントロール: MenuItem
Name: menuHelp
Text: ヘルプ(&H)

コントロール: MenuItem
Name: menuAbout
Text: バージョン情報(&A)...
Clickイベント: menuAbout_Click
}}

次に前号で作成したPluginInfoクラスをプロジェクトに追加します。ただし、CreateInstanceメソッドでIPluginHostオブジェクトをIPluginHost.Hostプロパティに設定するように変更しています。

PluginInfoクラスは次のようなコードです。

#code(vbnet){{
Imports System

''' <summary>
''' プラグインに関する情報
''' </summary>
Public Class PluginInfo
    Private _location As String
    Private _className As String

    ''' <summary>
    ''' PluginInfoクラスのコンストラクタ
    ''' </summary>
    ''' <param name="path">アセンブリファイルのパス</param>
    ''' <param name="cls">クラスの名前</param>
    Private Sub New(ByVal path As String, ByVal cls As String)
        Me._location = path
        Me._className = cls
    End Sub

    ''' <summary>
    ''' アセンブリファイルのパス
    ''' </summary>
    Public ReadOnly Property Location() As String
        Get
            Return _location
        End Get
    End Property

    ''' <summary>
    ''' クラスの名前
    ''' </summary>
    Public ReadOnly Property ClassName() As String
        Get
            Return _className
        End Get
    End Property

    ''' <summary>
    ''' 有効なプラグインを探す
    ''' </summary>
    ''' <returns>有効なプラグインのPluginInfo配列</returns>
    Public Shared Function FindPlugins() As PluginInfo()
        Dim plugins As New System.Collections.ArrayList
        'IPlugin型の名前
        Dim ipluginName As String = _
            GetType(Plugin.IPlugin).FullName

        'プラグインフォルダ
        Dim folder As String = _
            System.IO.Path.GetDirectoryName( _
            System.Reflection.Assembly. _
            GetExecutingAssembly().Location)
        folder += "\plugins"
        If Not System.IO.Directory.Exists(folder) Then
            Throw New ApplicationException( _
                "プラグインフォルダ""" + folder + _
                """が見つかりませんでした。")
        End If

        '.dllファイルを探す
        Dim dlls As String() = _
            System.IO.Directory.GetFiles(folder, "*.dll")

        Dim dll As String
        For Each dll In dlls
            Try
                'アセンブリとして読み込む
                Dim asm As System.Reflection.Assembly = _
                    System.Reflection.Assembly.LoadFrom(dll)
                Dim t As Type
                For Each t In asm.GetTypes()
                    'アセンブリ内のすべての型について、
                    'プラグインとして有効か調べる
                    If t.IsClass And t.IsPublic And _
                        Not t.IsAbstract And _
                        Not (t.GetInterface(ipluginName) Is Nothing _
                        ) Then
                        'PluginInfoをコレクションに追加する
                        plugins.Add(New PluginInfo(dll, t.FullName))
                    End If
                Next t
            Catch
            End Try
        Next dll

        'コレクションを配列にして返す
        Return CType(plugins.ToArray( _
            GetType(PluginInfo)), PluginInfo())
    End Function 'FindPlugins

    ''' <summary>
    ''' プラグインクラスのインスタンスを作成する
    ''' </summary>
    ''' <returns>プラグインクラスのインスタンス</returns>
    Public Function CreateInstance( _
        ByVal host As Plugin.IPluginHost) As Plugin.IPlugin
        Try
            'アセンブリを読み込む
            Dim asm As System.Reflection.Assembly = _
                System.Reflection.Assembly.LoadFrom(Me.Location)
            'クラス名からインスタンスを作成する
            Dim plugin As Plugin.IPlugin = _
                CType(asm.CreateInstance(Me.ClassName), _
                    Plugin.IPlugin)
            'IPluginHostの設定
            plugin.Host = host
            Return plugin
        Catch
        End Try
    End Function
End Class
}}

#code(csharp){{
using System;

namespace MainApplication
{
	/// <summary>
	/// プラグインに関する情報
	/// </summary>
	public class PluginInfo
	{
		private string _location;
		private string _className;

		/// <summary>
		/// PluginInfoクラスのコンストラクタ
		/// </summary>
		/// <param name="path">アセンブリファイルのパス</param>
		/// <param name="cls">クラスの名前</param>
		private PluginInfo(string path, string cls)
		{
			this._location = path;
			this._className = cls;
		}

		/// <summary>
		/// アセンブリファイルのパス
		/// </summary>
		public string Location
		{
			get {return _location;}
		}

		/// <summary>
		/// クラスの名前
		/// </summary>
		public string ClassName
		{
			get {return _className;}
		}

		/// <summary>
		/// 有効なプラグインを探す
		/// </summary>
		/// <returns>有効なプラグインのPluginInfo配列</returns>
		public static PluginInfo[] FindPlugins()
		{
			System.Collections.ArrayList plugins =
				new System.Collections.ArrayList();
			//IPlugin型の名前
			string ipluginName = typeof(Plugin.IPlugin).FullName;

			//プラグインフォルダ
			string folder = System.IO.Path.GetDirectoryName(
				System.Reflection.Assembly
				.GetExecutingAssembly().Location);
			folder += "\\plugins";
			if (!System.IO.Directory.Exists(folder))
				throw new ApplicationException(
					"プラグインフォルダ\"" + folder +
					"\"が見つかりませんでした。");

			//.dllファイルを探す
			string[] dlls =
				System.IO.Directory.GetFiles(folder, "*.dll");

			foreach (string dll in dlls)
			{
				try
				{
					//アセンブリとして読み込む
					System.Reflection.Assembly asm =
						System.Reflection.Assembly.LoadFrom(dll);
					foreach (Type t in asm.GetTypes())
					{
						//アセンブリ内のすべての型について、
						//プラグインとして有効か調べる
						if (t.IsClass && t.IsPublic && !t.IsAbstract &&
							t.GetInterface(ipluginName) != null)
						{
							//PluginInfoをコレクションに追加する
							plugins.Add(
								new PluginInfo(dll, t.FullName));
						}
					}
				}
				catch
				{
				}
			}

			//コレクションを配列にして返す
			return (PluginInfo[]) plugins.ToArray(typeof(PluginInfo));
		}

		/// <summary>
		/// プラグインクラスのインスタンスを作成する
		/// </summary>
		/// <returns>プラグインクラスのインスタンス</returns>
		public Plugin.IPlugin CreateInstance(Plugin.IPluginHost host)
		{
			try
			{
				//アセンブリを読み込む
				System.Reflection.Assembly asm =
					System.Reflection.Assembly.LoadFrom(this.Location);
				//クラス名からインスタンスを作成する
				Plugin.IPlugin plugin =
					(Plugin.IPlugin) asm.CreateInstance(this.ClassName);
				//IPluginHostの設定
				plugin.Host = host;
				return plugin;
			}
			catch
			{
				return null;
			}
		}
	}
}
}}

IPluginHostインターフェイスは、フォームクラスで実装します。また、プラグインの読み込みと、メニューへの表示はフォームのLoadイベントハンドラで行い、メニューを選択することにより、プラグインを実行できるようにします。

以下に変更を加えたフォームクラスの主要部分のコード(Windowsフォームデザイナが作成したコードを除く)を示します。

#code(vbnet){{
Public Class Form1
    Inherits System.Windows.Forms.Form
    Implements Plugin.IPluginHost

	'(省略)

    'IPluginの配列
    Dim plugins() As Plugin.IPlugin

    'IPluginHostの実装
    Public ReadOnly Property MainForm() As Form _
        Implements Plugin.IPluginHost.MainForm
        Get
            Return Me
        End Get
    End Property

    Public ReadOnly Property RichTextBox() As RichTextBox _
        Implements Plugin.IPluginHost.RichTextBox
        Get
            Return mainRichTextBox
        End Get
    End Property

    Public Sub ShowMessage(ByVal plugin As Plugin.IPlugin, _
        ByVal msg As String) _
        Implements Plugin.IPluginHost.ShowMessage
        'ステータスバーに表示する
        mainStatusbar.Text = msg
    End Sub

    'メインフォームのLoadイベントハンドラ
    Private Sub Form1_Load(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles MyBase.Load
        'インストールされているプラグインを探す
        Dim pis As PluginInfo() = PluginInfo.FindPlugins()

        'プラグインのインスタンスを取得する
        Me.plugins = New Plugin.IPlugin(pis.Length - 1) {}
        Dim i As Integer
        For i = 0 To (Me.plugins.Length) - 1
            Me.plugins(i) = pis(i).CreateInstance(Me)
        Next i

        'プラグインを実行するメニューを追加する
        Dim plugin As Plugin.IPlugin
        For Each plugin In Me.plugins
            Dim mi As New MenuItem(plugin.Name, _
                AddressOf menuPlugin_Click)
            Me.menuPlugins.MenuItems.Add(mi)
        Next plugin
    End Sub

    'プラグインのメニューがクリックされた時
    Private Sub menuPlugin_Click(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles menuPlugins.Click
        Dim mi As MenuItem = CType(sender, MenuItem)
        'クリックされたプラグインを探す
        '(同じ名前のプラグインが複数あると困ったことに...)
        Dim plugin As Plugin.IPlugin
        For Each plugin In Me.plugins
            If mi.Text = plugin.Name Then
                'クリックされたプラグインを実行する
                plugin.Run()
                Return
            End If
        Next plugin
    End Sub

    'プラグインのバージョンを表示する
    Private Sub menuAbout_Click(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles menuAbout.Click
        Dim msg As String = "インストールされているプラグイン" + vbLf _
            + "(名前 : 説明 : バージョン)" + vbLf + vbLf
        Dim plugin As Plugin.IPlugin
        For Each plugin In Me.plugins
            msg += String.Format("{0} : {1} : {2}" + vbLf, _
                plugin.Name, plugin.Description, plugin.Version)
        Next plugin
        MessageBox.Show(msg)
    End Sub
End Class
}}

#code(csharp){{
namespace MainApplication
{
	/// <summary>
	/// Form1 の概要の説明です。
	/// </summary>
	public class Form1 : System.Windows.Forms.Form, Plugin.IPluginHost
	{
		//(省略)

		//IPluginの配列
		Plugin.IPlugin[] plugins;

		//IPluginHostの実装
		public Form MainForm
		{
			get
			{
				return (Form) this;
			}
		}
		public RichTextBox RichTextBox
		{
			get
			{
				return mainRichTextBox;
			}
		}
		public void ShowMessage(Plugin.IPlugin plugin, string msg)
		{
			//ステータスバーに表示する
			mainStatusbar.Text = msg;
		}

		//メインフォームのLoadイベントハンドラ
		private void Form1_Load(object sender, System.EventArgs e)
		{
			//インストールされているプラグインを探す
			PluginInfo[] pis = PluginInfo.FindPlugins();

			//プラグインのインスタンスを取得する
			this.plugins = new Plugin.IPlugin[pis.Length];
			for (int i = 0; i < this.plugins.Length; i++)
				this.plugins[i] = pis[i].CreateInstance(this);

			//プラグインを実行するメニューを追加する
			foreach (Plugin.IPlugin plugin in this.plugins)
			{
				MenuItem mi = new MenuItem(plugin.Name, 
					new EventHandler(menuPlugin_Click));
				this.menuPlugins.MenuItems.Add(mi);
			}
		}

		//プラグインのメニューがクリックされた時
		private void menuPlugin_Click(
			object sender, System.EventArgs e)
		{
			MenuItem mi = (MenuItem) sender;
			//クリックされたプラグインを探す
			//(同じ名前のプラグインが複数あると困ったことに...)
			foreach (Plugin.IPlugin plugin in this.plugins)
			{
				if (mi.Text == plugin.Name)
				{
					//クリックされたプラグインを実行する
					plugin.Run();
					return;
				}
			}
		}

		//プラグインのバージョンを表示する
		private void menuAbout_Click(
			object sender, System.EventArgs e)
		{
			string msg = "インストールされているプラグイン\n"
				+ "(名前 : 説明 : バージョン)\n\n";
			foreach (Plugin.IPlugin plugin in this.plugins)
			{
				msg += string.Format("{0} : {1} : {2}\n",
					plugin.Name, plugin.Description, plugin.Version);
			}
			MessageBox.Show(msg);
		}
	}
}
}}

このメインアプリケーションを実行させるには、実行ファイルのあるフォルダに"plugins"というフォルダを作り、そこに前に作成したプラグイン"CountChars.dll"と"FindString.dll"をコピーしてください。うまくいくと、「プラグイン」メニューに「文字数取得」と「文字列の検索」が追加され、プラグインの機能を呼び出すことができるようになります。

以上でプラグイン機能を実現させる方法に関する解説はおしまいです。ここで紹介した知識を応用することにより、より複雑なプラグインも作成できるでしょう。この記事を読んで、プラグインを使ったアプリケーションを作ってみようと思われる方が一人でもいらっしゃるならばうれしいのですが。

**コメント [#x62023d5]
#comment

//これより下は編集しないでください
#pageinfo([[:Category/.NET]],2004-08-31 (火) 06:00:00,DOBON!,2010-03-21 (日) 02:40:46,DOBON!)
[ トップ ]   [ 新規 | 子ページ作成 | 一覧 | 単語検索 | 最終更新 | ヘルプ ]