我很难绕过我在这里阅读的有关使用AppDomains卸载插件DLL的一些答案。这是我的架构:
在我的解决方案中,我有一个包含SharedObjects
类的ModuleBase
项目,所有插件(解决方案中的单独项目)都继承。在SharedObjects
项目中,我还有一个所有插件都实现的接口(所以如果我有六个插件,它们都实现了相同的接口,因此使用这些插件的主程序不需要知道甚至不关心什么插件类的名称是编译时的名称;它们都实现了相同的接口,因此暴露了相同的信息)。每个插件项目都有一个对SharedObjects
项目的项目引用。 (作为附注,可能不重要,可能不是 - SharedObjects
项目具有对另一个解决方案的项目引用,CompanyObjects
包含许多常用的类,类型,对象等。当所有的说法和完成时,当任何给定的插件编译时,输出目录包含以下DLL:
SharedObjects
项目CompanyObjects
项目CompanyObjects
项目我的主程序创建了一个对类的引用,我正在进行所有与插件相关的工作(该类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,没有任何依赖性错误吗?
答案 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都放在他们需要的文件夹中。
对不起,如果这有点长,但这有点复杂。也许有一个更短的方法来做到这一点......但此刻,这让我得到了我需要的一切。