4

hereのように C# でこれが行われているのを見たことがありますが、VB.NET でこれを行う方法を理解できないようです。背景として、カスタム ComboBox コントロールを .dll として作成しましたが、それを別の .dll(ArcMap コンポーネント) に実装する必要があります。

残念ながら、ArcMap では「サード パーティ」の DLL をコンポーネントと共に読み込むことはできません。これは、アドインでサード パーティのアセンブリを参照するオプションがないためです。

誰かが私を正しい方向に向けることができれば、それは大歓迎です。

4

2 に答える 2

9

Visual Studio 2008 の VB.NET でこの手法を使用しています...

まず、プロジェクトは、「その他」の 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

このモジュールをプロジェクトのどこかに配置し、DLL 内の他のコードを呼び出す前にメソッドを呼び出してハンドラーEnsureInitializedをアタッチしてください。AssemblyResolve

注: [CONTAINER ASSEMBLY] を dll の名前に置き換える必要があります。

また、上記のコードは、戦略的な場所に log4net ロギング メッセージが含まれているため、実際に使用するものを簡略化したものであることに注意してください。ロギング メッセージは実際の機能には必要ないため、簡潔さと明確さのために削除しました。

このアプローチの主な注意点は、AssemblyResolveハンドラーを手動でアタッチする必要があることです。EnsureInitialized消費コードの初期化中に一度だけ呼び出されるように設定できない場合でも、EnsureInitialized実行に「他の」dll を必要とする独自のモジュール内で呼び出すことができます。これにより、初期化呼び出しを行うことを覚えておく必要があるため、コードが少し複雑になりますが、必要なときに dll が利用できることを知っているので、夜は眠ることができます。

私の経験では、一部の「その他」の dll は、埋め込みリソースとして提供されるとうまく動作しないため、動作させるには少し試してみる必要があるかもしれません。

最終的な注意: ArcMap コンポーネントを使用したことがないため、マイレージは異なる場合があります。

于 2012-03-13T05:39:13.977 に答える
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 メソッドは拡張メソッドであり、モジュールでのみ作成できるためです。

EmbeddedAssemblyResolverClassあとは、リソースに埋め込まれたアセンブリを使用するアプリケーション内の他のクラスでを作成してインスタンス化するだけです。

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

埋め込みリソースからメソッドを呼び出すと、最初にアセンブリが現在の AppDomain に既に読み込まれているかどうかが確認されます。読み込まれている場合は、アセンブリが返されます。埋め込まれたアセンブリが読み込まれていない場合は、埋め込まれたリソースから動的に読み込まれます (存在する場合)。

このコードの優れている点の 1 つは、クラス ライブラリのように EntryPoint を持たないアセンブリで機能することです。また、このコードを使用した埋め込みアセンブリを使用して、埋め込みアセンブリを読み込むことに成功しました。

于 2016-11-06T04:13:26.683 に答える