2012-03-13 5 views
4

Я видел это в C#, например here, хотя я не могу понять, как это сделать в VB.NET. Для некоторого фона я создал настраиваемый элемент управления ComboBox как .dll, и мне нужно его реализовать в другом .dll (ArcMap Component).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.

ПРИМЕЧАНИЕ. Вам необходимо заменить [CONTAINER ASSEMBLY] на имя вашей DLL.

Также обратите внимание, что приведенный выше код является урезанной версией того, что мы на самом деле используем, потому что наша система включает log4net протоколирование сообщений в стратегических местах. Журнальные сообщения не нужны для истинной функциональности, поэтому я удалил их для краткости и ясности.

Основная оговорка к этому подходу заключается в том, что обработчик AssemblyResolve должен быть подключен вручную. Даже если вы не можете настроить так, чтобы EnsureInitialized вызывается только один раз во время инициализации кода потребления, вы можете вызвать EnsureInitialized в любом из ваших собственных модулей, для выполнения которого требуется «другая» dll. Это делает код немного более тонким, потому что вы должны помнить, чтобы сделать этот вызов инициализации, но он позволяет вам спать по ночам, зная, что DLL будет доступна, когда вам это нужно.

По моему опыту, некоторые «другие» DLL-устройства плохо воспроизводятся, когда они предусмотрены как встроенные ресурсы, поэтому вам может понадобиться немного поиграть, чтобы заставить все работать.

Заключительное примечание: я никогда не использовал компонент ArcMap, поэтому ваш пробег может варьироваться!

+0

TLS, спасибо. Я обнаружил некоторые странные ошибки в коде, который вы предоставили. «FormatWith» не является членом System.String, «First» не является членом System.Array, а «ToBytes» не является членом System.IO.Stream. Любые идеи, что я мог бы заменить этими функциями? –

+0

Извините! Я очистил его, но не пошел достаточно далеко. В нашем коде есть некоторые методы расширения, которые я забыл перевести обратно в стандартные методы. Я обновил код и добавил определение «ToBytes», чтобы завершить этот пример. – TLS

+0

Мой ответ по существу является конверсией [этого ответа C#] (http://stackoverflow.com/a/97290/475820). Я вижу это после рассмотрения этого ответа после размещения моего. Теперь у нас есть C# и версия VB! – TLS

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 

The EmbeddedAssemblyResolverClass используется для создания фактического обработчика AssemblyResolve событий. Я добавил несколько колоколов и свистов, добавив поддержку IDisposable и события для Initialized и Uninitialized, но вы можете обрезать их, если не хотите.

Я создал остальную часть кода в EmbeddedAssemblyResolverModule, чтобы они были глобальными для моей сборки, а также потому, что метод LoadEmbeddedAssembly является методом расширения, который может быть создан только в модулях.

Теперь остается только создать и создать экземпляр EmbeddedAssemblyResolverClass в любом другом классе приложения, которое использует сборку, встроенную в ее ресурсы.

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

После того как вы вызываете метод из встроенного ресурса он будет сначала посмотреть, чтобы увидеть, если сборка уже загружена в текущем домене приложения, если это то сборка возвращается. Если встроенная сборка не была загружена, она будет динамически загружаться из встроенных ресурсов, если она существует.

Одна вещь, которая хороша в этом коде, заключается в том, что она работает с сборками, которые не имеют EntryPoint, например библиотеки классов. Также у меня был успех при загрузке встроенных сборок со встроенными сборками, которые также использовали этот код.

Смежные вопросы