2012-03-13 96 views
4

我已經看到這在C#中完成,如here雖然,我似乎無法弄清楚如何在VB.NET中做到這一點。對於某些背景,我創建了一個自定義ComboBox控件作爲.dll,並且需要在另一個.dll(ArcMap組件)中實現它。VB.NET嵌入式DLL在另一個DLL中作爲嵌入式資源?

不幸的是,ArcMap不容許「第三方」的DLL與組件一起被加載,因爲沒有選項來引用任何第三方組件爲您的加載項。

如果有人能指出我正確的方向,它將不勝感激。

回答

9

我們使用VB.NET這種技術在Visual Studio 2008 ...

首先,該項目需要知道包括「其他」 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 

將這個模塊在某處你的項目,並確保前致電EnsureInitialized方法來連接AssemblyResolve處理調用您的DLL任何其他代碼。

注意:您需要使用您的DLL的名稱替換[容器組件。

還要注意的是,上面的代碼是什麼,我們實際使用的,因爲我們包括戰略要地log4net的日誌消息的精簡版本。日誌消息不是真正的功能所必需的,所以爲了簡潔和清晰起見,我將它們刪除了。

主要告誡這種做法是,AssemblyResolve處理程序必須手動連接。即使你不能設置,所以EnsureInitialized在消費代碼的初始化期間只被調用一次,你可以在任何你自己的模塊中調用EnsureInitialized,這些模塊需要執行「其他」dll。這會使代碼變得更加微妙,因爲您必須記住進行初始化調用,但它確實允許您在晚上睡覺,知道該DLL在您需要時可用。

以我的經驗,一些「其他」的dll沒有發揮好,當他們作爲嵌入的資源提供的,因此你可能需要了一下週圍玩把事情的工作。

Final注意:我從未使用ArcMap組件,所以您的里程可能會有所不同!

+0

TLS,非常感謝。我在你提供的代碼中發現了一些奇怪的錯誤。 「FormatWith」不是System.String的成員,「First」不是System.Array的成員,「ToBytes」不是System.IO.Stream的成員。任何想法,我可以用這些功能替代? – 2012-03-13 05:55:40

+0

對不起!我清理了它,但沒有足夠的。在我們的代碼中有一些擴展方法,我忘記了將其轉換回標準方法。我更新了代碼並添加了「ToBytes」的定義來完成示例。 – TLS 2012-03-13 14:21:36

+0

我的答案本質上是[本C#答案](http://stackoverflow.com/a/97290/475820)的轉換。我在發佈了我的回覆後查看了這個答案後,我看到了這個。現在我們有一個C#和一個VB版本! – TLS 2012-03-13 14:25:26

0

我採取了一點不同的方法。我想要的東西幾乎可以自動初始化,並在使用嵌入式程序集時動態加載嵌入式程序集。我也想避免加載已存在於當前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方法是擴展方法,只能在Modules中創建。

現在唯一要做的就是在應用程序的任何其他類中創建並實例化EmbeddedAssemblyResolverClass,該類使用嵌入其資源中的程序集。

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

一旦從嵌入的資源調用的方法會先看看如果大會在當前AppDomain中已經加載,如果是,則返回大會。如果嵌入式程序集尚未加載,則它將從嵌入式資源動態加載(如果存在)。

這段代碼很好的一件事是它可以在沒有EntryPoint的程序集上工作,就像類庫一樣。另外,我也成功地使用嵌入式程序集來加載嵌入式程序集,並使用此代碼。