─━ IT ━─

.NET에 의한 플러그인 기능을 가진 텍스트 에디터 작성

DKel 2021. 8. 16. 00:13
반응형
처음에 Adobe Photoshop이나 Becky! Internet Mail 등의 애플리케이션에서는 「플러그 인」(또는, 「애드 인」, 「익스텐션」등 )이라고 불리는 프로그램을 인스톨 하는 것으로써, 기능을 확장할 수 있습니다.이 기사에서는 이러한 플러그 인 기능을 가진 애플리케이션의 만드는 방법을, 플러그 인 대응의 텍스트 에디터를 작성하는 것으로써, 설명합니다.여기서 소개하는 플러그인 기능은, Becky!와 같이, 플러그 인 본체인 DLL 파일을 지정된 폴더에 카피하는 것으로, 플러그 인을 사용하는 애플리케이션(호스트)이 자동적으로 플러그 인을 인식하는 것입니다.또한 플러그인 기능의 해설이 목적이기 때문에 텍스트 에디터는 폼에 Rich Text Box를 붙인 것뿐인 빈약한 것이므로 텍스트 에디터 작성에 참고는 되지 않습니다.대상 독자 .NET 프로그래밍을 진행한 적이 있거나 .NET 프로그래밍에 관심이 있는 분을 대상으로 합니다.기본적인 사항에 대해서는 설명하고 있지 않으므로, 불명확한 점은 MSDN 라이브러리 등에서 조사해 주십시오.리플렉션에 관해서는 DOBON.NET.NET Tips에서도 설명하고 있으니 참고하시기 바랍니다.필요한 환경샘플은 Visual Studio .NET 2003에서 작성을 하고, .NET Framework 1.1에서 동작을 하고 있는데, .NET Framework 1.0에서도 문제가 되지 않습니다.플러그인 기능을 실현하기 위한 기본적인 생각 보통 DLL 어셈블리 파일을 다른 어셈블리에서 사용하려면 미리 그 어셈블리를 참조에 추가해 둘 필요가 있습니다(Visual Studio .NET의 경우는 [프로젝트]→ [참조 추가...]를 실행하고 대화상자에서 추가할 수 있습니다.NET SDK의 경우는 컴파일 옵션 /reference그러나 플러그인의 경우는 컴파일시에 참조할 수 없기 때문에 실행시에 불러와야 합니다.이것을 가능하게 하는 것이, 「리플렉션」입니다.리플렉션을 이용하면 실행 시 DLL 내 형태의 멤버에 액세스 할 수 있습니다.따라서, 플러그 인의 기능을 호스트로부터 호출할 때의 메서드명, 파라메타, 반환값등을 「약속사」로서 정해 두면, 호스트로부터 플러그 인의 기능을 호출할 수 있게 됩니다.이상과 같은 방법으로 플러그 인 기능의 실현은 충분히 가능합니다.그러나, 호스트와 플러그 인 사이에 정해진 「약속사」가 애매해서는, 알기 어렵고, 위험합니다.이약속을확실히하기위해서는인터페이스를사용하는것이좋겠습니다.즉 플러그인이 꼭 가져야 할 메서드나 속성을 인터페이스에서 정의해 놓고 플러그인은 이 인터페이스를 구현한 것이어야 한다고 봅니다.이와 같이 해 두면, 플러그 인을 작성하는 측에서는 무엇을 하지 않으면 안 되는 것인지 명확하게 되어, 호스트의 측에서는 플러그 인을 지정한 인터페이스의 형태로서 취급할 수 있습니다.마찬가지로 호스트에서 실장해야 할 인터페이스를 정의해 두는 것으로써, 플러그 인으로부터 호스트의 기능을 호출하거나 결과를 콜백 하는 것이 용이해집니다.인터페이스 작성 먼저 인터페이스를 작성합니다.Visual Studio .NET에서는 클래스 라이브러리의 프로젝트를 작성하고 플러그인으로 구현해야 할 인터페이스(IPlugin)와 호스트에서 구현해야 할 인터페이스(IPlugin Host)를 정의합니다. NET SDK의 경우는 ′/target:library′ 옵션을 사용하여 컴파일합니다. 샘플에서는 프로젝트 ′Plugin Host′에서 ′Plugin′이라고 하는 ′일부턴트′을 선택합니다.′
namespace Plugin
{
/// < summary >
/// 플러그인으로 구현하는 인터페이스
/// < / summary >
public interface IPlugin
{
/// < summary >
/// 플러그인 이름
/// < / summary >
string Name { get ; }

/// < summary >
/// 플러그인을 실행하다
/// < / summary >
void Run ( ) ;

/// < summary >
/// 플러그인 인스턴스 생성 직후 호출되는 메서드
/// < / summary >
/// 플러그인 호스트
void Initialize ( IPluginHost host ) ;

//(이하 생략)
}

/// < summary >
/// 플러그인 호스트로 구현하는 인터페이스
/// < / summary >
public interface IPluginHost
{
/// < summary >
/// 호스트의 RichTextBox 컨트롤
/// < / summary >
RichTextBox RichTextBox { get ; }

/// < summary >
/// 호스트에서 메시지 표시하기
/// < / summary >
/// 메서드를 호출하는 플러그인
/// 표시하는 메세지
void ShowMessage ( IPlugin plugin , string msg ) ;

//(이하 생략)
}
} 「 IPlugin . vb 」 ( VB . NET )
Namespace Plugin
′′′ < summary >
′′플러그인으로 구현하는 인터페이스
′′′ < / summary >
Public Interface IPlugin
′′′ < summary >
′′플러그인 이름
′′′ < / summary >
ReadOnly Property Name ( ) As String

′′′ < summary >
′′플러그인을 실행하다
′′′ < / summary >
Sub Run ( )

′′′ < summary >
′′플러그인 인스턴스 작성 직후 호출되는 메서드
′′′ < / summary >
′′′ 플러그인 호스트
Sub Initialize ( ByVal host As IPluginHost )

(이하 생략)
End Interface

′′′ < summary >
′′플러그인 호스트로 구현하는 인터페이스
′′′ < / summary >
Public Interface IPluginHost
′′′ < summary >
′′ 호스트의 RichTextBox 컨트롤
′′′ < / summary >
ReadOnly Property RichTextBox ( ) As RichTextBox

′′′ < summary >
′′호스트로 메시지 표시하기
′′′ < / summary >
′′′ 메서드를 호출하는 플러그인
′′′ 표시하는 메시지
Sub ShowMessage ( ByVal plugin As IPlugin , ByVal msg As String )

(이하 생략)
End Interface
End Namespace 플러그인 작성 다음에 IPlugin 인터페이스를 구현함으로써 플러그인을 작성합니다.방금 전과 마찬가지로 클래스 라이브러리의 프로젝트를 새로 작성하고 ′Plugin.dll′을 참조하여 IPlugin을 구현한 클래스를 만듭니다.샘플에는 3개의 플러그인 프로젝트(′Count Char′, ′Find String′, ′File History′)가 포함되어 있습니다.이 중 가장 단순한 문장의 글자수만 세는 플러그인인 Count Char 플러그인의 코드 일부를 아래에 나타냅니다.「 CountChars.cs 」 ( C# )
namespace CountChars
{
/// < summary >
/// 글자 수 표시를 위한 플러그인
/// < / summary >
public class CountChars : Plugin . IPlugin
{
private Plugin . IPluginHost _ host ;

public string Name
{
get
{
return ′글자 수 취득′;
}
}

public void Initialize ( Plugin . IPluginHost host )
{
this . _ host = host ;
}

//(이하 생략)
}
} 「 CountChars . vb 」 ( VB . NET )
Namespace CountChars
′′′ < summary >
′′글자 수를 표시하기 위한 플러그인
′′′ < / summary >
Public Class CountChars
Implements Plugin . IPlugin

Private _ host As Plugin . IPluginHost

Public ReadOnly Property Name ( ) As String _
Implements Plugin . IPlugin . Name

Get
Return ′글자 수 취득′
End Get
End Property

Public Sub Initialize ( ByVal host As Plugin . IPluginHost ) _
Implements Plugin . IPlugin . Initialize

Me . _ host = host
End Sub

(이하 생략)
End Class
End Namespace Find String 플러그인은 폼을 표시하는 플러그인의 샘플입니다.검색 대화상자를 표시하여 지정된 문자열을 검색합니다.File History 플러그 인에 관해서는, 나중에 설명합니다.호스트의 작성 호스트 응용 프로그램은 Windows 응용 프로그램으로 만들고 폼에 RichTextBox와 MainMenu, StatusBar 컨트롤을 배치합니다.게다가 IPlug in Host 인터페이스를 실장합니다.샘플에서는 프로젝트 「Text Editor Form」이 호스트입니다.C#
namespace PluginTextEditor
{
public class TextEditorForm :
System . Windows . Forms . Form , Plugin . IPluginHost
{
public RichTextBox RichTextBox
{
get
{
return mainRichTextBox ;
}
}

public void ShowMessage ( Plugin . IPlugin plugin , string msg )
{
// 상태 바에 표시함
mainStatusbar . Text = msg ;
}

//(이하 생략)
}
} VB . NET
Namespace MainApplication
Public Class TextEditorForm
Inherits System . Windows . Forms . Form
Implements Plugin . IPluginHost

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

(이하 생략)
End Class
End Namespace 호스트 작성에서 문제가 되는 것은 유효한 플러그인을 어떻게 찾을지, 그리고 플러그인의 인스턴스를 어떻게 작성할지의 두 가지입니다.유효한 플러그인을 찾으려면 지정된 플러그인 폴더에 있는 DLL 파일을 Assembly.LoadFrom 메서드로 읽고 그 안에 포함된 형태를 열거하여 Type.GetInterface 메서드로 IPlugin 인터페이스를 구현한 클래스인지 알아보겠습니다.또한 플러그인 인스턴스를 작성하려면 Activator.CreateInstance메서드나 Assembly.CreateInstance메서드 등을 사용하면 됩니다.이 처리는 PluginInfo클래스에서 합니다.C#
using System ;

namespace PluginTextEditor
{
/// < summary >
/// 플러그인 관련 정보
/// < / summary >
public class PluginInfo
{
private string _ location ;
private string _ className ;

/// < summary >
/// PluginInfo클래스 컨스트럭터
/// < / summary >
/// 어셈블리 파일 경로
/// 클래스 이름
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 >
/// 유효한 플러그인의 PluginInfo배열
public static PluginInfo [ ] FindPlugins ( string pluginDir )
{
System . Collections . ArrayList plugins =
new System . Collections . ArrayList ( ) ;
//IPlugin형 이름
string ipluginName = typeof ( Plugin . IPlugin ) . FullName ;

if ( ! System . IO . Directory . Exists ( pluginDir ))
throw new ApplicationException (
′플러그인 폴더′′ + pluginDir +
′′을 찾을 수 없었습니다.′);

//.dll 파일찾기
string [ ] dlls =
System . IO . Directory . GetFiles ( pluginDir , ′ * . 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 >
/// 플러그인 클래스 인스턴스
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 ) ;
// 초기화
plugin . Initialize ( host ) ;
return plugin ;
}
catch
{
return null ;
}
}
}
} VB . NET
Imports System

Namespace MainApplication
′′′ < summary >
′′플러그인에 대한 정보
′′′ < / summary >
Public Class PluginInfo
Private _ location As String
Private _ className As String

′′′ < summary >
′′PluginInfo 클래스 컨스트럭터
′′′ < / summary >
′′′ 어셈블리 파일의 경로
′′′ 클래스 이름
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 >
′′′ 유효한 플러그인의 PluginInfo배열
Public Shared Function FindPlugins ( _
ByVal pluginDir As String ) As PluginInfo ( )
Dim plugins As New System . Collections . ArrayList
IPlugin형 이름
Dim ipluginName As String = _
GetType ( Plugin . IPlugin ) . FullName

If Not System . IO . Directory . Exists ( pluginDir ) Then
Throw New ApplicationException ( _
′플러그인 폴더′ ′+ pluginDir + _
′′를 찾을 수 없었습니다.′)
End If

.dll 파일 찾기
Dim dlls As String ( ) = _
System . IO . Directory . GetFiles ( pluginDir , ′ * . 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
Plugin Info를 컬렉션에 추가하다
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

′′′ < summary >
′′플러그인 클래스 인스턴스 만들기
′′′ < / summary >
′′′ 플러그인 클래스 인스턴스
Public Function CreateInstance ( _
ByVal host As Plugin . IPluginHost ) As Plugin . IPlugin
Try
′어셈블리를 읽어 들이다
Dim asm As System . Reflection . Assembly = _
System . Reflection . Assembly . LoadFrom ( Me . Location )
클래스 이름으로 instance를 작성하다
Dim plugin As plugin . IPlugin = _
CType ( asm . CreateInstance ( Me . ClassName ) , _
plugin . IPlugin )
′초기화
plugin . Initialize ( host )
Return plugin
Catch
Return Nothing
End Try
End Function
End Class
End Namespace 응용 이상이 플러그인 기능을 실현하는 기본적인 방법으로, 같은 사항이 아래에 제시된 참고자료의 1~6 링크에서도 소개되고 있습니다.여기에서는 그 응용으로서 사용할 만한 아이디어를 몇개인가 소개합니다.예를 들면 에디터로 파일을 열었을 때나 저장했을 때에, 그것을 플러그 인측에서 알 수 있으면, 플러그 인으로 할 수 있는 일이 퍼집니다.여기에서는 그 예로 IPlug in Host 인터페이스에 파일을 연 직후 발생하는 Opened File 이벤트를 추가합니다.C#
namespace Plugin
{
/// < summary >
/// Opened File 이벤트 델리게이트
/// < / summary >
public delegate void OpenedFileEventHandler (
object sender , OpenedFileEventArgs e ) ;

public interface IPluginHost
{
/// < summary >
/// 파일을 연 직후 발생하는 이벤트
/// < / summary >
event OpenedFileEventHandler OpenedFile ;
}

//(이하 생략)
} VB . NET
Namespace Plugin
′′′ < summary >
′′Opened File 이벤트 델리게이트
′′′ < / summary >
Public Delegate Sub OpenedFileEventHandler ( _
ByVal sender As Object , ByVal e As OpenedFileEventArgs )

Public Interface IPluginHost
′′′ < summary >
′′파일을 연 직후에 발생하는 이벤트
′′′ < / summary >
Event OpenedFile As OpenedFileEventHandler
End Interface

(이하 생략)
End Namespace 이 이벤트를 사용한 플러그인 예가 샘플 프로젝트 「File History」입니다.이 플러그 인에서는 에디터로 열린 파일의 패스를 보존하고 있습니다.플러그 인을 메뉴에 표시하는 경우, 그 MenuItem은 호스트에서 작성하는 것이 보통입니다(적어도 아래에 든 참고 자료의 샘플에서는 그렇게 되어 있습니다).그런데 IPlugin 인터페이스에 MenuItem형 속성을 추가하고 플러그인 쪽에서 MenuItem 객체를 만드는 방법도 있습니다.플러그인이 자신의 MenuItem을 관리해야 한다고 생각하면 오히려 이것이 좋습니다.샘플의 File History 플러그인에서는 서브메뉴를 가지는 MenuItem을 작성해 아래 그림과 같이 서브메뉴에서 선택한 파일을 열 수 있도록 하고 있습니다.플러그 인에 따라서는, 독자적인 설정이 필요한 것도 있을 것입니다.그래서 IPlugin 인터페이스에 ShowSetupDialog 메서드를 추가하고 플러그인 설정 대화상자를 띄울 수 있도록 하겠습니다.게다가 HasSetupDialog 속성을 추가해서 설정 대화상자가 있는지 없는지 취득할 수 있도록 하면 사전에 설정 유무를 알 수 있고 아래 그림과 같이 사용자가 플러그인을 선택했을 때 설정이 있으면 [설정] 버튼을 유효하게, 없으면 무효로 한다고 할 수 있게 됩니다.샘플내에서 설정이 있는 플러그 인의 예는, File History 플러그 인입니다.보충: 어플리케이션 설정이 TabControl에서 이루어질 때는 인터페이스가 자신의 설정을 위한 TabPage 오브젝트를 만들고 어플리케이션 설정에 추가할 수 있도록 하는 방법도 있을 것입니다.정리 이 기사에서는, .NET Framework에 의해 플러그 인 기능을 실현시키는 방법을, 동기능을 가진 텍스트 에디터의 작성을 예로 해설했습니다.플러그 인 기능 실현의 포인트를 간단하게 정리하면 다음과 같이 됩니다.플러그 인은 DLL로서 작성하고, 호스트에서는 리플렉션을 사용해 플러그 인의 기능을 호출한다.
플러그 인 및 호스트에서 반드시 실장해야 할 인터페이스를 미리 작성해 두면, 애매함을 배제할 수 있어 유익하다.『개발자 Fusion』 『플러그인 기반 애플리케이션 작성 - 소개』 Tim Dawson 』
SAD 개발자.Net → PluginFX - C#에서 확장 가능한 플러그인 프레임워크는 → → → → → 2004→1′
C# Shoki를 이용한 Code Project 『Plugin Architecture the2003 88 the
코드 프로젝트 【C#】 [빨간색 플러그인 】 [2004】5】
DevSource → C#이 있는 플러그인 구축.NET: Part 1 〈Nathan Good〉〈2004〉5〉
.netプログラミング研究 『第39、40号』 どぼん! 著
반응형