我已经在 C# 中看到过此操作,例如此处,但我似乎无法弄清楚如何在 VB.NET 中执行此操作。对于某些背景,我创建了一个自定义 ComboBox 控件作为 .dll,我需要在另一个 .dll(ArcMap 组件)中实现它。
不幸的是,ArcMap 不允许将“第三方”DLL 与组件一起加载,因为没有选项可以为您的加载项引用任何第三方程序集。
如果有人能指出我正确的方向,那将不胜感激。
我已经在 C# 中看到过此操作,例如此处,但我似乎无法弄清楚如何在 VB.NET 中执行此操作。对于某些背景,我创建了一个自定义 ComboBox 控件作为 .dll,我需要在另一个 .dll(ArcMap 组件)中实现它。
不幸的是,ArcMap 不允许将“第三方”DLL 与组件一起加载,因为没有选项可以为您的加载项引用任何第三方程序集。
如果有人能指出我正确的方向,那将不胜感激。
我们在 Visual Studio 2008 的 VB.NET 中使用此技术...
首先,项目需要知道将“其他”dll 作为嵌入式资源。在解决方案资源管理器中,将 dll 作为文件添加到您的项目中(而不是作为参考)。然后,打开文件的属性并将构建操作设置为“嵌入式资源”。建议您在项目结构中创建 dll 文件的本地副本,而不是链接到其他位置。一旦项目包含 dll 文件,您就可以添加对该 dll 副本的引用,以便您可以在设计时使用其内容。
这确保了“其他” dll 包含在您编译的 dll 中,但它不会在需要时自动加载。这就是以下代码的来源:
Public Module Core
Private _initialized As Boolean
Public Sub EnsureInitialized()
If Not _initialized Then
AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf AssemblyResolve
_initialized = True
End If
End Sub
Private Function AssemblyResolve(ByVal sender As Object, ByVal e As ResolveEventArgs) As Assembly
Dim resourceFullName As String = String.Format("[CONTAINER ASSEMBLY].{0}.dll", e.Name.Split(","c)(0))
Dim thisAssembly As Assembly = Assembly.GetExecutingAssembly()
Using resource As Stream = thisAssembly.GetManifestResourceStream(resourceFullName)
If resource IsNot Nothing Then Return Assembly.Load(ToBytes(resource))
Return Nothing
End Using
End Function
Private Function ToBytes(ByVal instance As Stream) As Byte()
Dim capacity As Integer = If(instance.CanSeek, Convert.ToInt32(instance.Length), 0)
Using result As New MemoryStream(capacity)
Dim readLength As Integer
Dim buffer(4096) As Byte
Do
readLength = instance.Read(buffer, 0, buffer.Length)
result.Write(buffer, 0, readLength)
Loop While readLength > 0
Return result.ToArray()
End Using
End Function
End Module
将此模块放在项目中的某个位置,并确保在调用 dll 中的任何其他代码之前EnsureInitialized
调用该方法以附加AssemblyResolve
处理程序。
注意:您需要将 [CONTAINER ASSEMBLY] 替换为您的 dll 名称。
另请注意,上面的代码是我们实际使用的精简版本,因为我们的代码在战略位置包含 log4net 日志消息。真正的功能不需要日志消息,所以为了简洁明了,我删除了它们。
这种方法的主要警告是AssemblyResolve
必须手动附加处理程序。即使您无法进行设置,以便EnsureInitialized
在使用代码的初始化期间仅调用一次,您也可以EnsureInitialized
在您自己的任何需要“其他”dll 执行的模块中调用。这使代码更加精细,因为您必须记住进行初始化调用,但它确实允许您在晚上睡觉,因为知道 dll 将在您需要时可用。
以我的经验,一些“其他” dll 在作为嵌入式资源提供时无法正常运行,因此您可能需要尝试一下才能使事情正常运行。
最后说明:我从未使用过 ArcMap 组件,因此您的里程可能会有所不同!
我采取了一些不同的方法。我想要一些在使用时几乎可以自动初始化和动态加载嵌入式程序集的东西。我还想避免加载当前 AppDomain 中已经存在的程序集的多个实例。下面的代码为我完成了所有这些。
Imports System.Reflection
Imports System.Runtime.CompilerServices
''' <summary>
''' This class initializes a special AssemblyResolve handler for assemblies embedded in the current assembly's resources. <para/>
''' To auto initialize create a variable as a New EmbeddedAssemblyResolverClass in any class using an embedded assembly.
''' </summary>
Public Class EmbeddedAssemblyResolverClass
Implements IDisposable
''' <summary>
''' Initialization flag.
''' </summary>
''' <returns>[Boolean]</returns>
Public ReadOnly Property Initialized As Boolean
''' <summary>
''' Raised when successfully initialized.
''' </summary>
Public Event Initilized()
''' <summary>
''' Raised when successfully uninitialized.
''' </summary>
Public Event Uninitilized()
Sub New()
Try
If Not Initialized Then
AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf ResolveAppDomainAssemblies
Initialized = True
RaiseEvent Initilized()
End If
Catch ex As Exception
'Maybe some error logging in the future.
MsgBox(ex.Message)
End Try
End Sub
#Region "IDisposable Support"
Private disposedValue As Boolean ' To detect redundant calls
' IDisposable
Protected Overridable Sub Dispose(disposing As Boolean)
If Not disposedValue Then
If disposing Then
RemoveHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf ResolveAppDomainAssemblies
_Initialized = False
RaiseEvent Uninitilized()
End If
End If
disposedValue = True
End Sub
' This code added by Visual Basic to correctly implement the disposable pattern.
Public Sub Dispose() Implements IDisposable.Dispose
' Do not change this code. Put cleanup code in Dispose(disposing As Boolean) above.
Dispose(True)
End Sub
#End Region
End Class
Public Module EmbeddedAssemblyResolverModule
''' <summary>
''' Returns a dictionary of assemblies loaded in the current AppDomain by full name as key.
''' </summary>
''' <returns>[Dictionary(Of String, Assembly)]</returns>
Public ReadOnly Property AppDomainAssemblies As Dictionary(Of String, Assembly)
Get
Return AppDomain.CurrentDomain.GetAssemblies.ToDictionary(Function(a) a.FullName)
End Get
End Property
''' <summary>
''' Method that attempts to resolve assemblies already loaded to the current AppDomain.
''' </summary>
''' <param name="sender">[Object]</param>
''' <param name="args">[ResolveEventArgs]</param>
''' <returns>[Assembly]</returns>
Public Function ResolveAppDomainAssemblies(sender As Object, args As ResolveEventArgs) As Assembly
'Return the existing assembly if it has already been loaded into the current AppDomain.
If AppDomainAssemblies.ContainsKey(args.Name) Then Return AppDomainAssemblies.Item(args.Name)
'Build the potential embedded resource name.
Dim ResourceName As String = String.Format("{0}.{1}.dll", Assembly.GetExecutingAssembly().FullName.Split(",").First, args.Name.Split(",").First)
'Attempt to load the requested assembly from the current assembly's embedded resources.
Return Assembly.GetExecutingAssembly.LoadEmbeddedAssembly(ResourceName)
End Function
''' <summary>
''' Loads an assembly from the current assembly's embedded resources.
''' </summary>
''' <param name="CurrentAssembly">[Assembly] Current assembly which contains the embedded assembly.</param>
''' <param name="EmbeddedAssemblyName">[String] Full name of the embedded assembly.</param>
''' <returns>[Assembly]</returns>
<Extension>
Public Function LoadEmbeddedAssembly(CurrentAssembly As Assembly, EmbeddedAssemblyName As String) As Assembly
'Return the existing assembly if it has already been loaded into the current AppDomain.
If AppDomainAssemblies.ContainsKey(EmbeddedAssemblyName) Then Return AppDomainAssemblies.Item(EmbeddedAssemblyName)
'Attempt to load the requested assembly from the current assembly's embedded resources.
Using Stream = CurrentAssembly.GetManifestResourceStream(EmbeddedAssemblyName)
If Stream Is Nothing Then Return Nothing
Dim RawAssembly As [Byte]() = New [Byte](Stream.Length - 1) {}
Stream.Read(RawAssembly, 0, RawAssembly.Length)
Return Assembly.Load(RawAssembly)
End Using
End Function
End Module
EmbeddedAssemblyResolverClass
用于创建实际的 AssemblyResolve 事件处理程序。我通过为 Initialized 和 Uninitialized 添加 IDisposable 支持和事件添加了一些花里胡哨,但如果不需要,您可以将其删除。
我在 中创建了其余代码,EmbeddedAssemblyResolverModule
以便它们对我的程序集是全局的,并且因为 LoadEmbeddedAssembly 方法是一个扩展方法,它只能在模块中创建。
现在唯一要做的就是EmbeddedAssemblyResolverClass
在应用程序中使用嵌入到其资源中的程序集的任何其他类中创建和实例化 。
'''' <summary>
'''' Used to auto initialize the EmbeddedAssemblyResolverClass.
'''' </summary>
Public WithEvents EAR As New EmbeddedAssemblyResolverClass
从嵌入式资源调用方法后,它将首先查看程序集是否已加载到当前 AppDomain 中,如果已加载,则返回程序集。如果嵌入式程序集尚未加载,则将从嵌入式资源中动态加载(如果存在)。
这段代码的一个优点是它适用于没有入口点的程序集,比如类库。此外,我在使用也使用此代码的嵌入式程序集加载嵌入式程序集方面也取得了成功。