0

AppDomains を使用したプラグイン DLL のアンロードについてここで読んだいくつかの回答に頭を悩ませています。これが私のアーキテクチャです:

私のソリューションでは、すべてのプラグイン (ソリューション内の個別のプロジェクト) が継承するクラスをSharedObjects含むプロジェクトがあります。ModuleBaseプロジェクトには、SharedObjectsすべてのプラグインが実装するインターフェイスもあります (したがって、6 つのプラグインがある場合、それらはすべて同じインターフェイスを実装するため、これらのプラグインを使用するメイン プログラムは、プラグインのクラスの名前を知る必要も、気にする必要さえありません)それらはすべて同じインターフェースを実装しているため、同じ情報を公開します)。各プラグイン プロジェクトには、プロジェクトへのプロジェクト参照がありSharedObjectsます。(補足として、重要かもしれませんが、そうでないかもしれません-そのSharedObjectsプロジェクトには別のソリューションへのプロジェクト参照があり、CompanyObjects一般的に使用される多くのクラス、タイプ、オブジェクトなどが含まれています) すべてが完了したら、特定のプラグインがコンパイルされると、出力ディレクトリには次の DLL が含まれます。

  1. プラグイン自体のコンパイル済み DLL
  2. SharedObjectsプロジェクトの DLL
  3. CompanyObjectsプロジェクトの DLL
  4. CompanyObjectsプロジェクトで参照される 4 つの前提条件のサードパーティ DLL

私のメイン プログラムは、プラグイン関連のすべての作業を行っているクラスへの参照を作成します (そのクラスはプロジェクトPluginHelpersに保存されSharedObjectsます)。ユーザーが DLL ファイルを選択できるように、プログラムは OpenFileDialog を提供します。現在実行中なので、プラグイン DLL だけを別のフォルダーに移動し、Assembly.LoadFrom(PathToDLL)ステートメントを使用してそれらを読み込むことができます。エラーなしで読み込まれます。プロジェクトにインターフェイスが実装されていることを確認し、SharedObjects基本的な情報を収集し、プラグイン DLL 自体でバックグラウンド作業を初期化して、インターフェイスが公開するものを持っていることを確認します。LoadFrom問題は、これらの DLL を使用するとすぐにロックされるため、最初にメイン プログラムを終了しないとこれらの DLL をアップグレードできないことです。

この MSDN サイトから、ロックされた DLL の問題に対する解決策を見つけました。しかし、OPで機能するコードを使用して、OPと同じ「ファイルまたは依存関係が見つかりません」というエラーが発生します。これらの DLL の残りを含むリリース フォルダーから DLL を開くと、エラーが発生します。

FusionLog はさらに混乱を招きます。開こうとしていたパスについては言及されていません。メインプログラムをデバッグしているディレクトリを検索しようとしています。これは、プラグインとはまったく異なるパスにある完全に異なるプロジェクトであり、探しているファイルはDLLの名前ですが、プログラムが実行されています。この時点で、指定したパスを無視して、まったく別のフォルダーで DLL を探している理由がわかりません。

参考までに、ここに私のLoaderクラスと、DLL をロードする (試行する) ために使用しているコードを示します。

Private Class Loader
    Inherits MarshalByRefObject

    Private _assembly As [Assembly]
    Public ReadOnly Property TheAssembly As [Assembly]
        Get
            Return _assembly
        End Get
    End Property

    Public Overrides Function InitializeLifetimeService() As Object
        Return Nothing
    End Function

    Public Sub LoadAssembly(ByVal path As String)
        _assembly = Assembly.Load(AssemblyName.GetAssemblyName(path))
    End Sub

    Public Function GetAssembly(ByVal path As String) As Assembly
        Return Assembly.Load(AssemblyName.GetAssemblyName(path))    'this doesn't throw an error
    End Function
End Class

Public Sub Add2(ByVal PathToDll As String)
    Dim ad As AppDomain = AppDomain.CreateDomain("TempPluginDomain")
    Dim l As Loader = ad.CreateInstanceAndUnwrap(
        GetType(Loader).Assembly.FullName,
        GetType(Loader).FullName
    )
    Dim theDll As Assembly = l.GetAssembly(PathToDll)    'error happens here
    'there's another way to do it that makes the exact point of the error clear:
    'Dim theDll As Assembly = Nothing
    'l.LoadAssembly(PathToDll)    'No problems here. The _assembly variable is successfully set
    'theDll = l.TheAssembly       'Here's where the error occurs, as soon as you try to read that _assembly variable.
    AppDomain.Unload(ad)
End Sub

DLL を必要に応じてのみ、依存関係エラーなしでロードおよびアンロードできるように、誰かが私を正しい方向に向けることができますか?

4

1 に答える 1

1

やっと手に入れたと思います。最終的にはいくつかのことになりました - 共有 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 が必要なフォルダーに配置されます。

少し長くて申し訳ありませんが、これは少し複雑です。もっと短い方法があるかもしれませんが、現時点では、これで必要なものはすべて揃っています。

于 2012-05-16T21:41:16.363 に答える