┏第39号━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ .NETプログラミング研究 ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
──<メニュー>───────────────────────
■.NET Tips
・プラグイン機能を持つアプリケーションを作成する - その1
───────────────────────────────
───────────────────────────────
■.NET Tips
───────────────────────────────
●プラグイン機能を持つアプリケーションを作成する - その1
Adobe PhotoshopやBecky! Internet Mailなどのアプリケーションで
は、「プラグイン」(または、「アドイン」、「エクステンション」
等)と呼ばれるプログラムをインストールすることにより、機能を追
加することができるようになっています。ここでは、このようなプラ
グイン機能を持ったアプリケーションの作り方を考えます。
[URL]Adobe Photoshop
http://www.adobe.co.jp/products/photoshop/main.html
[URL]RimArts - Becky! Internet Mail
http://www.rimarts.co.jp/becky-j.htm
(プラグインが何だか分からないという方は、「アスキー デジタル
用語辞典」や「IT用語辞典 e-Words」等をご覧ください。)
[URL]ASCII24 - アスキー デジタル用語辞典 - プラグイン
http://yougo.ascii24.com/gh/67/006745.html
[URL]IT用語辞典 e-Words : プラグイン 【plug-in】
http://e-words.jp/w/E38397E383A9E382B0E382A4E383B3.html
早速ですが、プラグイン機能の実現のために参考になりそうな記事を
以下にいくつか紹介します。
[URL]Developer Fusion - Writing Plugin-Based Applications
http://www.developerfusion.com/show/4371/
[URL]DevSource - Building Plug-ins with C# .NET
http://www.devsource.ziffdavis.com/article2/0,1759,1594318,00.asp
[URL]SA Developer .Net - PluginFX - An extensible plugin
framework in C#
http://www.sadeveloper.net/viewarticle.aspx?articleID=143
[URL]The Code Project - Plugin Architecture using C#
http://www.codeproject.com/csharp/C__Plugin_Architecture.asp
[URL]The Code Project - Plug-ins in C#
http://www.codeproject.com/csharp/PluginsInCSharp.asp
これらの記事で紹介されている方法は基本的にはほとんど同じで、そ
れは、インターフェイスを使用するという方法です。つまり、プラグ
インとして必要となる機能をプロパティやメソッド(さらに、イベン
トやインデクサ)として持つインターフェイスをあらかじめ用意して
おき、このインターフェイスを実装したクラスとしてプラグインを作
成するのです。
★インターフェイスの作成
理屈は置いておき、実際にプラグインを作成してみましょう。
ここで作成するプラグインは、プラグインの名前を返す機能と、渡さ
れた文字列を処理して、結果を文字列として返す機能を有するものと
し、それぞれの機能のためにNameプロパティとRunメソッドを持つイ
ンターフェイスを作ります。
Visual Studio .NETでは、クラスライブラリのプロジェクトを作成し、
次のようなコードを書き、ビルドします。.NET SDKの場合は、
/target:libraryコンパイラオプションにより、コードライブラリを
作成します。なお、ここで作成されたアセンブリファイル名は、
"Plugin.dll"であるとします。
(Visual Studio .NETのVB.NETの場合は、プロジェクトのプロパティ
の「ルート名前空間」が空白になっているものとします。デフォルト
ではプロジェクト名となっています。)
‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥
Imports System
Namespace Plugin
Public Interface IPlugin
ReadOnly Property Name() As String
Function Run(ByVal str As String) As String
End Interface
End Namespace
‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥
‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥
using System;
namespace Plugin
{
public interface IPlugin
{
string Name {get;}
string Run(string str);
}
}
‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥
★プラグインの作成
次に、このインターフェイスを基に、プラグインを作ります。プラグ
インもクラスライブラリとして作成します。プラグインは今作成した
IPluginインターフェイスを実装する必要がありますので、"Plugin.
dll"を参照に追加します。具体的には、Visual Studio .NETの場合は、
ソリューションエクスプローラの「参照設定」に"Plugin.dll"を追加
します。.NET SDKの場合は、/referenceコンパイラオプションを使用
します。
ここでは渡された文字列の文字数を返すプラグインを作成することに
します。IPluginインターフェイスを実装した次のようなCountChars
クラスを作成します。このクラスライブラリは"CountChars.dll"とい
う名前のアセンブリファイルとしてビルドするものとします。
‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥
Imports System
Namespace CountChars
Public Class CountChars
Implements Plugin.IPlugin
Public ReadOnly Property Name1() As String _
Implements Plugin.IPlugin.Name
Get
Return "文字数取得"
End Get
End Property
Public Function Run1(ByVal str As String) As String _
Implements Plugin.IPlugin.Run
Return str.Length.ToString()
End Function
End Class
End Namespace
‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥
‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥
using System;
namespace CountChars
{
public class CountChars : Plugin.IPlugin
{
public string Name
{
get
{
return "文字数取得";
}
}
public string Run(string str)
{
return str.Length.ToString();
}
}
}
‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥
★プラグインを使うアプリケーションの作成
残るはプラグインを使用するメインアプリケーションの作成です。メ
インアプリケーションでは、インストールされている有効なプラグイ
ンをどのように探すか、そして、プラグインクラスのインスタンスを
どのように作成するかということが問題となります。
まずインストールされているプラグインを探す方法についてです。プ
ラグインは必ず".dll"という拡張子を持ち、指定されたフォルダに置
かれていなければならないという約束にしておきます(ここでは、メ
インアプリケーションがインストールされているフォルダにある
"plugins"フォルダにプラグインを置くものとします)。これにより、
メインアプリケーションはプラグインフォルダにある".dll"の拡張子
を持つファイルのみを調べればよいことになります。さらに、アセン
ブリにIPluginインターフェイスを実装したクラスがあるか調べるに
は、リフレクションを使用します。
またプラグインクラスのインスタンスを作成するには、Activator.
CreateInstanceメソッドなどを使用すればよいでしょう(下のサンプ
ルでは、Assembly.CreateInstanceメソッドを使用しています)。
それでは実際に作成してみましょう。ここではメインアプリはコンソー
ルアプリケーションとして作成します。また、"Plugin.dll"を参照に
追加します。
まずはプラグインを扱うクラス("PluginInfo")を作成します。このク
ラスは、プラグインのアセンブリファイルのパスとクラス名を返すプ
ロパティ(それぞれ"Location"と"ClassName")と、有効なプラグイ
ンを探すメソッド("FindPlugins")、IPluginのインスタンスを作成
するメソッド("CreateInstance")を持ちます。このクラスのコード
を以下に示します。
‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥
Imports System
Namespace MainApplication
'''
''' プラグインに関する情報
'''
Public Class PluginInfo
Private _location As String
Private _className As String
'''
''' PluginInfoクラスのコンストラクタ
'''
''' アセンブリファイルのパス
''' クラスの名前
Private Sub New(ByVal path As String, ByVal cls As String)
Me._location = path
Me._className = cls
End Sub
'''
''' アセンブリファイルのパス
'''
Public ReadOnly Property Location() As String
Get
Return _location
End Get
End Property
'''
''' クラスの名前
'''
Public ReadOnly Property ClassName() As String
Get
Return _className
End Get
End Property
'''
''' 有効なプラグインを探す
'''
''' 有効なプラグインのPluginInfo配列
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 AndAlso t.IsPublic AndAlso _
Not t.IsAbstract AndAlso _
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
'''
''' プラグインクラスのインスタンスを作成する
'''
''' プラグインクラスのインスタンス
Public Function CreateInstance() As Plugin.IPlugin
Try
'アセンブリを読み込む
Dim asm As System.Reflection.Assembly = _
System.Reflection.Assembly.LoadFrom(Me.Location)
'クラス名からインスタンスを作成する
Return CType(asm.CreateInstance(Me.ClassName), _
Plugin.IPlugin)
Catch
End Try
End Function
End Class
End Namespace
‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥
‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥
using System;
namespace Plugin
{
///
/// プラグインに関する情報
///
public class PluginInfo
{
private string _location;
private string _className;
///
/// PluginInfoクラスのコンストラクタ
///
/// アセンブリファイルのパス
/// クラスの名前
private PluginInfo(string path, string cls)
{
this.Location = path;
this.ClassName = cls;
}
///
/// アセンブリファイルのパス
///
public string Location
{
get {return _location;}
}
///
/// クラスの名前
///
public string ClassName
{
get {return _className;}
}
///
/// 有効なプラグインを探す
///
/// 有効なプラグインのPluginInfo配列
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));
}
///
/// プラグインクラスのインスタンスを作成する
///
/// プラグインクラスのインスタンス
public Plugin.IPlugin CreateInstance()
{
try
{
//アセンブリを読み込む
System.Reflection.Assembly asm =
System.Reflection.Assembly.LoadFrom(this.Location);
//クラス名からインスタンスを作成する
return (Plugin.IPlugin)
asm.CreateInstance(this.ClassName);
}
catch
{
return null;
}
}
}
}
‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥
FindPluginsメソッドが多少複雑ですので、簡単に説明しておきます。
FindPluginsメソッドでは、Assembly.LoadFromメソッドでアセンブリ
を読み込んだ後、アセンブリ内のすべての型について、その型がクラ
スであり、パブリックであり、抽象クラスでないことを確認し、さら
にType.GetInterfaceメソッドにより、IPluginインターフェイスを実
装していることを確認します。これらすべての条件に当てはまった場
合に有効なプラグインと判断します。(よってこの例では、1つのア
センブリファイルに複数のプラグインクラスを定義することができま
す。)
ここまで来たら、後は簡単です。PluginInfoクラスを使ったメインの
クラス(エントリポイント)のサンプルは、次のようになります。
‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥
Imports System
Namespace MainApplication
Public Class PluginHost
'/
'/ エントリポイント
'/
_
Public Shared Sub Main()
'インストールされているプラグインを調べる
Dim pis As PluginInfo() = PluginInfo.FindPlugins()
'すべてのプラグインクラスのインスタンスを作成する
Dim plugins(pis.Length - 1) As Plugin.IPlugin
Dim i As Integer
For i = 0 To plugins.Length - 1
plugins(i) = pis(i).CreateInstance()
Next i
'有効なプラグインを表示し、使用するプラグインを選ばせる
Dim number As Integer = -1
Do
For i = 0 To plugins.Length - 1
Console.WriteLine("{0}:{1}", i, plugins(i).Name)
Next i
Console.WriteLine( _
"使用するプラグインの番号を入力してください。:")
Try
number = Integer.Parse(Console.ReadLine())
Catch
End Try
Loop While number < 0 Or number >= pis.Length
Console.WriteLine(plugins(number).Name + " を使用します。")
'プラグインに渡す文字列の入力を促す
Console.WriteLine("文字列を入力してください。:")
Dim str As String = Console.ReadLine()
'プラグインのRunメソッドを呼び出して結果を取得する
Dim result As String = plugins(number).Run(str)
'結果の表示
Console.WriteLine("結果:")
Console.WriteLine(result)
Console.ReadLine()
End Sub
End Class
End Namespace
‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥
‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥
using System;
namespace MainApplication
{
public class PluginHost
{
///
/// エントリポイント
///
[STAThread]
static void Main(string[] args)
{
//インストールされているプラグインを調べる
PluginInfo[] pis = Plugin.PluginInfo.FindPlugins();
//すべてのプラグインクラスのインスタンスを作成する
Plugin.IPlugin[] plugins = new Plugin.IPlugin[pis.Length];
for (int i = 0; i < plugins.Length; i++)
plugins[i] = pis[i].CreateInstance();
//有効なプラグインを表示し、使用するプラグインを選ばせる
int number = -1;
do
{
for (int i = 0; i < plugins.Length; i++)
Console.WriteLine("{0}:{1}", i, plugins[i].Name);
Console.WriteLine(
"使用するプラグインの番号を入力してください。:");
try
{
number = int.Parse(Console.ReadLine());
}
catch
{
Console.WriteLine("数字を入力してください。");
}
} while (number < 0 || number >= pis.Length);
Console.WriteLine(plugins[number].Name + " を使用します。");
//プラグインに渡す文字列の入力を促す
Console.WriteLine("文字列を入力してください。:");
string str = Console.ReadLine();
//プラグインのRunメソッドを呼び出して結果を取得する
string result = plugins[number].Run(str);
//結果の表示
Console.WriteLine("結果:");
Console.WriteLine(result);
Console.ReadLine();
}
}
}
‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥
メインアプリ実行前に実行ファイルのあるフォルダに"plugins"とい
うフォルダを作り、そこに先ほど作成した"CountChars.dll"をコピー
しておいてください。これで、"CountChars.dll"をプラグインとして
認識できます。
基本的なプラグイン機能の実現方法は以上です。しかしより高度なプ
ラグイン機能が必要な場合には、これだけでは不十分かもしれません。
例えば、プラグインからメインアプリにフィードバックしたいなど、
プラグインからメインアプリケーションの機能(メソッド)を呼び出
したいケースもあるでしょう。そのような場合には、プラグインを使
う側で実装するインターフェイスを定義するという方法があります(実
は一番初めに紹介した「プラグイン機能の実現のために参考になりそ
うな記事」のすべてでこの方法が使われています)。
この方法については、次回、Windowsアプリケーションを例に紹介す
る予定です。
===============================
■このマガジンの購読、購読中止、バックナンバー、説明に関しては
次のページをご覧ください。
http://www.mag2.com/m/0000104516.htm
■発行人・編集人:どぼん!
(Microsoft MVP for Visual Basic, Oct 2003-Oct 2004)
http://dobon.net
dobon_info@yahoo.co.jp
■ご質問等はメールではなく、掲示板へお願いいたします。
http://dobon.net/vb/bbs.html
■上記メールアドレスへのメールは確実に読まれる保障はありません
(スパム、ウィルス対策です)。メールは下記URLのフォームメール
から送信してください。
http://dobon.net/mail.html
Copyright (c) 2003 - 2004 DOBON! All rights reserved.
===============================