2012-03-13 3 views
4

나는 VB.net에서 이것을 수행하는 방법을 알아 내지 못했지만, C#에서는 here과 같은 것을 보았습니다. 일부 배경에서는 .dll로 사용자 지정 ComboBox 컨트롤을 만들었으며 다른 .dll (ArcMap Component)에서이 컨트롤을 구현해야합니다.VB.NET 임베디드 DLL을 임베디드 리소스로 다른 DLL에 임베드합니까?

아쉽게도 ArcMap에서는 추가 기능에 대한 타사 어셈블리를 참조 할 수있는 옵션이 없기 때문에 ArcMap에서 해당 구성 요소와 함께 "타사"DLL을로드 할 수 없습니다.

누군가가 올바른 방향으로 나를 가리킬 수 있다면, 그것은 높이 평가 될 것입니다.

답변

9

우리는이 프로젝트가 포함 리소스로 "다른"DLL을 포함하는 알 필요

먼저 ... 비주얼 스튜디오 2008에서 VB.NET에서이 기술을 사용합니다. 솔루션 탐색기에서 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의 다른 코드를 호출 전에 AssemblyResolve 핸들러 을 첨부 할 EnsureInitialized 메서드를 호출해야합니다 : 다음 코드가 들어오는 곳이다.

참고 : [CONTAINER ASSEMBLY]를 dll 이름으로 바꿔야합니다.

또한 위 코드는 전략적 위치에 log4net 로깅 메시지가 포함되어 있기 때문에 실제로 사용하는 코드의 일부입니다. 로깅 메시지는 실제 기능에 필요하지 않으므로 간결하고 명확하게하기 위해 로깅 메시지를 제거했습니다.

이 접근 방식의 주된주의 사항은 AssemblyResolve 처리기를 수동으로 연결해야한다는 것입니다. EnsureInitialized을 소비 코드 초기화 중에 한 번만 호출하도록 설정할 수 없더라도 실행을 위해 "다른"dll을 필요로하는 자신의 모듈 내에서 EnsureInitialized을 호출 할 수 있습니다. 이렇게하면 초기화 호출을 기억해야하기 때문에 코드가 좀 더 섬세 해지지 만 필요할 때 dll을 사용할 수 있다는 사실을 알면 밤에 잠을 자도록 허용합니다.

제 경험상 일부 "다른"dll은 임베디드 리소스로 제공 될 때 제대로 작동하지 않으므로 제대로 작동하려면 약간의 게임이 필요할 수 있습니다.

최종 참고 사항 : 나는 ArcMap 구성 요소를 사용한 적이 없으므로 귀하의 마일리지가 달라질 수 있습니다!

+0

TLS, 정말 고마워요. 내가 제공 한 코드에 이상한 오류가 발견되었습니다. "FormatWith"는 System.String의 멤버가 아니며 "First"는 System.Array의 멤버가 아니며 "ToBytes"는 System.IO.Stream의 멤버가 아닙니다. 내가 그 기능을 대체 할 수있는 아이디어가 있습니까? –

+0

죄송합니다! 나는 그것을 정화했으나 충분히 멀리 가지 않았다. 우리가 코드에서 가지고있는 확장 메서드 중 일부는 표준 메서드로 다시 변환하는 것을 잊었습니다. 코드를 업데이트하고 예제를 완성하기 위해'ToBytes'의 정의를 추가했습니다. – TLS

+0

내 대답은 본질적으로 [this C# answer] (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 

EmbeddedAssemblyResolverClass

은 실제 AssemblyResolve 이벤트 핸들러를 생성하는 데 사용된다. IDisposable 지원 및 이벤트를 Initialized 및 Uninitialized에 추가하여 몇 가지 종소리와 휘파람을 추가했지만, 필요하지 않으면 해제 할 수 있습니다.

나머지 어셈블리 코드를 EmbeddedAssemblyResolverModule에 작성하여 내 어셈블리에 전역 적으로 적용 할 수 있으며 또한 LoadEmbeddedAssembly 메서드가 Extension 메서드이고 Modules에만 만들 수 있기 때문에 사용할 수 있습니다.

이제 할 일은 리소스에 포함 된 어셈블리를 사용하는 응용 프로그램의 다른 클래스에서 EmbeddedAssemblyResolverClass을 만들고 인스턴스화하는 것입니다.

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

이 포함 된 리소스에서 메소드를 호출하면 처음 어셈블리가 이미 현재 응용 프로그램 도메인에로드 된 경우 다음 어셈블리가 반환되는 경우, 참조 찾을 것입니다. 임베디드 어셈블리가로드되지 않은 경우 임베디드 어셈블리가있는 경우 동적으로로드됩니다.

이 코드에 대한 좋은 점 중 하나는 클래스 라이브러리와 같은 EntryPoint가없는 어셈블리에서 작동한다는 것입니다. 또한이 코드를 사용하는 어셈블리가 포함 된 임베디드 어셈블리를로드하는 데 성공했습니다.