やっと手に入れたと思います。最終的にはいくつかのことになりました - 共有 DLL をすべて 1 か所にまとめる必要があり、Hans が上で述べたように、アプリドメインを四角くする必要がありました。私のソリューション アーキテクチャは次のようになります。すべてのプラグイン プロジェクトを含むフォルダー。基本プラグイン アーキテクチャ用の 1 つのクラス ファイルと、「プラグイン ラッパー」クラスとサポート クラスを含む 2 番目のクラスを含む「共有オブジェクト」アセンブリ。そして、すべてを結び付けるコンソール アプリです。各プラグイン プロジェクトには、コンソール アプリと同様に、共有オブジェクト プロジェクトへのプロジェクト参照があります。プラグインを直接参照するものはありません。
したがって、共有オブジェクト プロジェクトには、PluginBase
クラスとIPlugin
インターフェイスのコードがあります。
Public Interface IPlugin
ReadOnly Property Result As Integer
Sub Calculate(ByVal param1 As Integer, ByVal param2 As Integer)
End Interface
Public MustInherit Class PluginBase
Inherits MarshalByRefObject
'None of this is necessary for the example to work, but I know I'll need to use an inherited base class later on so I threw it into the example now.
Protected ReadOnly Property PluginName As String
Get
Return CustomAttributes("AssemblyPluginNameAttribute")
End Get
End Property
Protected ReadOnly Property PluginGUID As String
Get
Return CustomAttributes("AssemblyPluginGUIDAttribute")
End Get
End Property
Protected IsInitialized As Boolean = False
Protected CustomAttributes As Dictionary(Of String, String)
Protected Sub Initialize()
CustomAttributes = New Dictionary(Of String, String)
Dim attribs = Me.GetType.Assembly.GetCustomAttributesData
For Each attrib In attribs
Dim name As String = attrib.Constructor.DeclaringType.Name
Dim value As String
If attrib.ConstructorArguments.Count = 0 Then
value = ""
Else
value = attrib.ConstructorArguments(0).ToString.Replace("""", "")
End If
CustomAttributes.Add(name, value)
Next
IsInitialized = True
End Sub
End Class
<AttributeUsage(AttributeTargets.Assembly)>
Public Class AssemblyPluginNameAttribute
Inherits System.Attribute
Private _name As String
Public Sub New(ByVal value As String)
_name = value
End Sub
Public Overridable ReadOnly Property PluginName As String
Get
Return _name
End Get
End Property
End Class
<AttributeUsage(AttributeTargets.Assembly)>
Public Class AssemblyPluginGUIDAttribute
Inherits System.Attribute
Private _g As String
Public Sub New(ByVal value As String)
_g = value
End Sub
Public Overridable ReadOnly Property PluginGUID As String
Get
Return _g
End Get
End Property
End Class
そして、サポートするクラスを含む PluginWrapper クラスがあります。
Imports System.IO
Imports System.Reflection
''' <summary>
''' The wrapper for plugin-related activities.
''' </summary>
''' <remarks>Each wrapper contains: the plugin; code to load and unload it from memory; and the publicly-exposed name and GUID of the plugin.</remarks>
Public Class PluginWrapper
Private _pluginAppDomain As AppDomain = Nothing
Private _isActive As Boolean = False
Private _plugin As IPlugin = Nothing
Private _pluginInfo As PluginInfo = Nothing
Private _pluginPath As String = ""
Public ReadOnly Property IsActive As Boolean
Get
Return _isActive
End Get
End Property
Public ReadOnly Property PluginInterface As IPlugin
Get
Return _plugin
End Get
End Property
Public ReadOnly Property PluginGUID As String
Get
Return _pluginInfo.PluginGUID
End Get
End Property
Public ReadOnly Property PluginName As String
Get
Return _pluginInfo.PluginName
End Get
End Property
Public Sub New(ByVal PathToPlugin As String)
_pluginPath = PathToPlugin
End Sub
Public Sub Load()
Dim l As New PluginLoader(_pluginPath)
_pluginInfo = l.LoadPlugin()
Dim setup As AppDomainSetup = New AppDomainSetup With {.ApplicationBase = System.IO.Directory.GetParent(_pluginPath).FullName}
_pluginAppDomain = AppDomain.CreateDomain(_pluginInfo.PluginName, Nothing, setup)
_plugin = _pluginAppDomain.CreateInstanceAndUnwrap(_pluginInfo.AssemblyName, _pluginInfo.TypeName)
_isActive = True
End Sub
Public Sub Unload()
If _isActive Then
AppDomain.Unload(_pluginAppDomain)
_plugin = Nothing
_pluginAppDomain = Nothing
_isActive = False
End If
End Sub
End Class
<Serializable()>
Public NotInheritable Class PluginInfo
Private _assemblyname As String
Public ReadOnly Property AssemblyName
Get
Return _assemblyname
End Get
End Property
Private _typename As String
Public ReadOnly Property TypeName
Get
Return _typename
End Get
End Property
Private _pluginname As String
Public ReadOnly Property PluginName As String
Get
Return _pluginname
End Get
End Property
Private _pluginguid As String
Public ReadOnly Property PluginGUID As String
Get
Return _pluginguid
End Get
End Property
Public Sub New(ByVal AssemblyName As String, ByVal TypeName As String, ByVal PluginName As String, ByVal PluginGUID As String)
_assemblyname = AssemblyName
_typename = TypeName
_pluginname = PluginName
_pluginguid = PluginGUID
End Sub
End Class
Public NotInheritable Class PluginLoader
Inherits MarshalByRefObject
Private _pluginBaseType As Type = Nothing
Private _pathToPlugin As String = ""
Public Sub New()
End Sub
Public Sub New(ByVal PathToPlugin As String)
_pathToPlugin = PathToPlugin
Dim ioAssemblyFile As String = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(_pathToPlugin), GetType(PluginBase).Assembly.GetName.Name) & ".dll")
Dim ioAssembly As Assembly = Assembly.LoadFrom(ioAssemblyFile)
_pluginBaseType = ioAssembly.GetType(GetType(PluginBase).FullName)
End Sub
Public Function LoadPlugin() As PluginInfo
Dim domain As AppDomain = Nothing
Try
domain = AppDomain.CreateDomain("Discovery")
Dim loader As PluginLoader = domain.CreateInstanceAndUnwrap(GetType(PluginLoader).Assembly.FullName, GetType(PluginLoader).FullName)
Return loader.Load(_pathToPlugin)
Finally
If Not IsNothing(domain) Then
AppDomain.Unload(domain)
End If
End Try
End Function
Private Function Load(ByVal PathToPlugin As String) As PluginInfo
Dim r As PluginInfo = Nothing
Try
Dim objAssembly As Assembly = Assembly.LoadFrom(PathToPlugin)
For Each objType As Type In objAssembly.GetTypes
If Not ((objType.Attributes And TypeAttributes.Abstract) = TypeAttributes.Abstract) Then
If Not objType.GetInterface("SharedObjects.IPlugin") Is Nothing Then
Dim attribs = objAssembly.GetCustomAttributes(False)
Dim pluginGuid As String = ""
Dim pluginName As String = ""
For Each attrib In attribs
Dim name As String = attrib.GetType.ToString
If name = "SharedObjects.AssemblyPluginGUIDAttribute" Then
pluginGuid = CType(attrib, AssemblyPluginGUIDAttribute).PluginGUID.ToString
ElseIf name = "SharedObjects.AssemblyPluginNameAttribute" Then
pluginName = CType(attrib, AssemblyPluginNameAttribute).PluginName.ToString
End If
If (Not pluginGuid = "") And (Not pluginName = "") Then
Exit For
End If
Next
r = New PluginInfo(objAssembly.FullName, objType.FullName, pluginName, pluginGuid)
End If
End If
Next
Catch f As FileNotFoundException
Throw f
Catch ex As Exception
'ignore non-valid dlls
End Try
Return r
End Function
End Class
最後に、各プラグイン プロジェクトは次のようになります。
Imports SharedObjects
<Assembly: AssemblyPluginName("Addition Plugin")>
<Assembly: AssemblyPluginGUID("{4EC46939-BD74-4665-A46A-C99133D8B2D2}")>
Public Class Plugin_Addition
Inherits SharedObjects.PluginBase
Implements SharedObjects.IPlugin
Private _result As Integer
#Region "Implemented"
Public Sub Calculate(ByVal param1 As Integer, ByVal param2 As Integer) Implements SharedObjects.IPlugin.Calculate
If Not IsInitialized Then
MyBase.Initialize()
End If
_result = param1 + param2
End Sub
Public ReadOnly Property Result As Integer Implements SharedObjects.IPlugin.Result
Get
Return _result
End Get
End Property
#End Region
End Class
すべてをセットアップするために、メイン プログラムはPluginWrapper
クラスの新しいインスタンスを作成し、DLL へのパスを指定してロードします。
Dim additionPlugin As New PluginWrapper("C:\path\to\Plugins\Plugin_Addition.dll")
additionPlugin.Load()
プログラムで必要なことをすべて実行したら...
additionPlugin.PluginInterface.Calculate(3, 2)
...そして結果を取得しています...
Console.WriteLine("3 + 2 = {0}", additionPlugin.PluginInterface.Result)
...プラグインをアンロードするだけです:
additionPlugin.Unload()
ラッパーがまだメモリ内にある間に再読み込みする必要がある場合は、Load()
メソッドを再度呼び出すだけで、新しい AppDomain を作成してアセンブリを再読み込みするために必要なすべての情報がそこにあります。そして、私の最初の質問への答えとして、Unload()
メソッドが呼び出されると、アセンブリが解放され、必要に応じて交換/アップグレードできるようになります。
以前につまずいたのは、プラグインと同じフォルダーに SharedObjects.dll ファイルを含めていなかったことです。私が見つけたのは、参照されているアセンブリが存在する必要があるということです。したがって、プラグインと共有オブジェクト プロジェクトの両方のビルド後のイベントでは、次のようになりますxcopy /y $(ProjectDir)$(OutDir)$(TargetFileName) c:\path\to\Plugins
。ソリューションをビルドするたびに、すべての DLL が必要なフォルダーに配置されます。
少し長くて申し訳ありませんが、これは少し複雑です。もっと短い方法があるかもしれませんが、現時点では、これで必要なものはすべて揃っています。