Встроенная DLL VB.NET в другую DLL как встроенный ресурс? - PullRequest
4 голосов
/ 13 марта 2012

Я видел, как это делается в C #, например, здесь , хотя я не могу понять, как это сделать в VB.NET.Для некоторого фона я создал пользовательский элемент управления ComboBox в виде .dll, и мне нужно реализовать его в другом .dll (компонент ArcMap).

К сожалению, ArcMap не допускает "сторонние" DLLзагружаться вместе с компонентом, потому что нет никакой возможности ссылаться на какие-либо сторонние сборки для вашей надстройки.

Если кто-то может указать мне правильное направление, это будет более чем оценено.

Ответы [ 2 ]

9 голосов
/ 13 марта 2012

Мы используем эту технику в 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 будет доступен, когда вам это нужно.

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

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

0 голосов
/ 06 ноября 2016

Я выбрал немного другой подход. Я хотел что-то, что в значительной степени автоматически инициализировалось и динамически загружало встроенные сборки по мере их использования. Я также хотел избежать загрузки нескольких экземпляров сборки, которая уже существовала в текущем домене приложений. Код ниже завершает все это для меня.

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 является методом Extension, который можно создавать только в модулях.

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

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

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

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

...