在VB.NET中将DLL作为嵌入式资源嵌入另一个DLL中?

4
我曾经在C#中见过这样的操作,例如此处,但我似乎无法弄清楚如何在VB.NET中实现这一点。提供一些背景信息,我已创建了一个自定义ComboBox控件作为.dll,并且需要将其实现到另一个.dll(ArcMap组件)中。
不幸的是,ArcMap不允许加载“第三方”DLL,因为没有选项可以引用任何第三方程序集来进行您的插件。
如果有人能指导我正确的方向,那将不胜感激。
2个回答

8
我们在 Visual Studio 2008 中使用 VB.NET 技术时采用此方法...
首先,项目需要知道将“其他”dll作为嵌入式资源进行包含。在 Solution Explorer 中,将该 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仅在消费代码的初始化期间调用,您也可以在任何需要执行“其他”dll的自己的模块中调用EnsureInitialized。这使得代码变得更加微妙,因为您必须记住进行该初始化调用,但它确实允许您在需要时知道该dll将可用。
根据我的经验,有些“其他”dll在作为嵌入式资源提供时表现不佳,因此您可能需要进行一些尝试才能使其正常工作。
最后注意:我从未使用过ArcMap组件,所以您的情况可能会有所不同!

非常抱歉!我已经清理过了,但是没有做到彻底。我们代码中有一些扩展方法,我忘记将它们转换回标准方法。我已经更新了代码,并添加了ToBytes的定义以完成示例。 - TLS
我的答案基本上是对这个C#答案的转换。我在发布自己的答案后,看到了这个。现在我们有一个C#版本和一个VB版本! - TLS
太棒了。非常感谢。在另一个类中调用时,我应该使用什么作为参数?对不起,问这么多问题。只是为了澄清,以便我第一次就做对。 - Logan B. Lehman
抱歉,我说的是AssemblyResolve。那是简单的AddHandler吗?还是它被实现到其他类中了? - Logan B. Lehman
好代码!但是 .net PDF 库 iTextSharp.dll 不起作用... http://stackoverflow.com/questions/21828299/itextsharp-net-dll-included-as-embedded-resource-does-not-work :( - Max
显示剩余6条评论

0
我采取了一个略有不同的方法。我想要一个能够自动初始化并动态加载嵌入式程序集的东西,以便在使用它们时进行加载。我还想避免在当前应用程序域中已存在的程序集中加载多个实例。下面的代码为我完成了所有这些任务。
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 事件处理程序。我添加了一些额外的功能,如 IDisposable 支持和 Initialized 和 Uninitialized 事件,但如果不需要可以将其删除。

我在 EmbeddedAssemblyResolverModule 中创建了其余的代码,这样它们就可以全局使用我的程序集,并且 LoadEmbeddedAssembly 方法是扩展方法,只能在模块中创建。

现在唯一剩下的事情就是在应用程序中使用嵌入到其资源中的程序集的任何其他类中创建和实例化 EmbeddedAssemblyResolverClass

'''' <summary>
'''' Used to auto initialize the EmbeddedAssemblyResolverClass.
'''' </summary>
Public WithEvents EAR As New EmbeddedAssemblyResolverClass

当您从嵌入式资源中调用方法时,它首先会查看程序集是否已在当前 AppDomain 中加载,如果是,则返回程序集。如果尚未加载嵌入的程序集,则将从嵌入的资源动态加载它(如果存在)。

这段代码的一个好处是,它适用于没有入口点(如类库)的程序集。此外,我成功地使用此代码加载了嵌入式程序集和使用此代码的嵌入式程序集。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接