卸载DLL直到需要它为止

时间:2012-05-04 20:46:40

标签: vb.net dll

我很难绕过我在这里阅读的有关使用AppDomains卸载插件DLL的一些答案。这是我的架构:

在我的解决方案中,我有一个包含SharedObjects类的ModuleBase项目,所有插件(解决方案中的单独项目)都继承。在SharedObjects项目中,我还有一个所有插件都实现的接口(所以如果我有六个插件,它们都实现了相同的接口,因此使用这些插件的主程序不需要知道甚至不关心什么插件类的名称是编译时的名称;它们都实现了相同的接口,因此暴露了相同的信息)。每个插件项目都有一个对SharedObjects项目的项目引用。 (作为附注,可能不重要,可能不是 - SharedObjects项目具有对另一个解决方案的项目引用,CompanyObjects包含许多常用的类,类型,对象等。当所有的说法和完成时,当任何给定的插件编译时,输出目录包含以下DLL:

  1. 插件本身的已编译DLL
  2. 来自SharedObjects项目
  3. 的DLL
  4. 来自CompanyObjects项目
  5. 的DLL
  6. CompanyObjects项目
  7. 中引用的四个必备第三方DLL

    我的主程序创建了一个对类的引用,我正在进行所有与插件相关的工作(该类PluginHelpers存储在SharedObjects项目中)。该程序提供OpenFileDialog,以便用户可以选择DLL文件。现在,当它正在运行时,我可以将插件DLL移动到单独的文件夹并使用Assembly.LoadFrom(PathToDLL)语句加载它们。它们加载没有错误;我检查以确保他们在SharedObjects项目中实现接口,收集一些基本信息,并初始化插件DLL本身的一些后台工作,以便接口有一些东西可以暴露。问题是,我不能在不退出主程序的情况下升级这些DLL,因为只要我使用LoadFrom这些DLL就被锁定了。

    this MSDN site我找到了锁定DLL问题的解决方案。但是我使用适用于OP的代码获得与OP相同的“文件或依赖性未找到”错误。当我从包含其余DLL的release文件夹中打开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,没有任何依赖性错误吗?

1 个答案:

答案 0 :(得分:1)

我想我终于明白了。它最终成为了一些东西 - 我需要在一个地方共享DLL,正如Hans上面提到的,我需要将我的appdomains平方。我的解决方案架构如下所示:包含所有插件项目的文件夹;一个“共享对象”程序集,其中一个类文件用于基本插件体系结构,另一个类包含我的“插件包装器”类和支持类;以及将所有内容联系在一起的控制台应用每个插件项目都有一个对共享对象项目的项目引用,控制台应用程序也是如此。没有什么能直接引用插件。

所以在我的共享对象项目中,我有我的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都放在他们需要的文件夹中。

对不起,如果这有点长,但这有点复杂。也许有一个更短的方法来做到这一点......但此刻,这让我得到了我需要的一切。