none
Ressource in dynamisch geladener DLL wird nicht gefunden RRS feed

  • Frage

  • Ich benutze VS2012 und .Net 4.0. Ich habe eine Klassenbibliothek DllA mit einem WPF UserControl Uc. DllA wird von einer Win-Forms-Anwendung verwendet. DllA ruft Uc auf.

    Liegen Main.exe und DllA.dll in einem Verzeichnis läuft es. Liegt DllA jedoch nicht als Datei vor, sondern wird dynamisch mit System.Reflection.Assembly.Load(Byte[]) geladen, bekomme ich zur Laufzeit folgende Fehlermeldung:

    Die Komponente "Namespace.Uc" verfügt nicht über eine Ressource, die vom URI "/DllA;component/uc.xaml" identifiziert wird.

    In dem Moment ist DllA geladen, denn dort wird Uc aufgerufen. Folgender Code in der Datei Uc.g.vb wird automatisch generiert.

    Dim uri as New System.Uri("/DllA;component/uc.xaml", System.UriKind.Relative)
    System.Windows.Application.LoadComponent(Me, uri)
    LoadComponent wirft die Ausnahme. Folgendes liefert mir jedoch erfolgreich Uc als Ressource, wird also gefunden.
    System.Windows.Application.GetResourceStream(uri)

    Ich kann Uc ebenfalls ohne Fehler aufrufen, bevor weitere Assemblies dynamisch geladen werden. Diese verwenden auch DllA.

    Wie kann ich erreichen, dass LoadComponent von Uc seine Ressource findet?

    Gruß Volker

    Montag, 29. April 2013 17:51

Antworten

  • Ich habe nochmal mein Beispielprojekt überarbeitet. Ich lade die Assemblies nach wie vor am Anfang der Methode, die geladenen Assemblies werden dann aber als Shared Objekte in der Klasse gespeichert. Wenn nun eine Assembly eine Referenzierte Assembly lädt, dann bekommt Sie die Assembly, die schon geladen wurde:
        Shared dllA As Assembly
        Shared dllB As Assembly
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    
            'Wenn Assemblies geladen werden, dieses laden beeinflussen
            AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf CurrentDomain_AssemblyResolve
    
            dllA = Reflection.Assembly.LoadFile("C:\Users\Tom\SkyDrive\Programmieren\Solution1\DllA\bin\Debug\DllA.dll")
            dllB = Reflection.Assembly.LoadFile("C:\Users\Tom\SkyDrive\Programmieren\Solution1\DllB\bin\Debug\DllB.dll")
    
            Dim kbType As Type = dllB.GetType("DllB.KlasseB")
            Dim kb As Object = Activator.CreateInstance(kbType)
    
            Dim ucType As Type = dllA.GetType("DllA.Uc")
    
            ElementHost1.Child = CType(Activator.CreateInstance(ucType), Windows.UIElement)
    
        End Sub
    
        Private Shared Function CurrentDomain_AssemblyResolve(sender As Object, e As ResolveEventArgs) As System.Reflection.Assembly
    
            If e.Name = dllA.FullName Then
                'DllA von Hand laden
                Return dllA
            End If
    
            'Andere DLL's ignorieren
            Return Nothing
    
        End Function
    Wichtig ist nun die korrekte reihenfolge des Ladens der DLL. Bei mir funktioniert alles Tadellos. Wenn es nicht so sein sollte. Hier ist die gesamte Projektmappe: http://sdrv.ms/18NxUtm
    Und poste ggf. nochmal was konkret du anders gemacht hast.

    <Code:13/> - Koopakiller [kuːpakɪllɐ]
    Webseite | Code Beispiele | Facebook | Snippets
    Wenn die Frage beantwortet ist, dann markiert die hilfreichsten Beiträge als Antwort und bewertet die Beiträge. Danke.
    Einen Konverter zwischen C# und VB.NET Code gibt es hier.

    • Als Antwort markiert drh1 Dienstag, 7. Mai 2013 12:09
    Montag, 6. Mai 2013 16:19
    Moderator
  • Vielen Dank Koopakiller! Mein Anwendungsfall läuft jetzt.

    Die entscheidende Idee ist, die Assemblies Shared zu speichern.

    Es ist zwar noch nicht geklärt, warum bereits geladene Assemblies nicht gefunden werden und das Ereignis AppDomain.CurrentDomain.AssemblyResolve ausgelöst wird. Jedoch funktioniert ein Workaround mit einem Dictionary:

        AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf AssemblyLaden
    
        Public Shared Function AssemblyLaden(sender As Object, e As ResolveEventArgs) _
            As Reflection.Assembly
    
            Static dict As New Dictionary(Of String, Reflection.Assembly)
    
            Dim stammname As String = (New Reflection.AssemblyName(e.Name)).Name
            Dim resourceName As String = "Namespace." & stammname & ".dll"
    
            If Not dict.ContainsKey(resourceName) Then
                Using stream As IO.Stream = Reflection.Assembly.GetExecutingAssembly() _
                    .GetManifestResourceStream(resourceName)
    
                    If stream IsNot Nothing Then
                        Dim assemblyData(CInt(stream.Length)) As Byte
                        stream.Read(assemblyData, 0, assemblyData.Length)
                        dict(resourceName) = Reflection.Assembly.Load(assemblyData)
                    End If
                End Using
            End If
    
            If dict.ContainsKey(resourceName) Then Return dict(resourceName)
    
            Return Nothing
        End Function

    Gruß Volker


    • Als Antwort markiert drh1 Dienstag, 7. Mai 2013 12:09
    • Bearbeitet drh1 Dienstag, 7. Mai 2013 13:12 Link ergänzt
    Dienstag, 7. Mai 2013 12:06

Alle Antworten

  • Danke für die Antwort. Die Threads hatte ich schon gefunden, habe jedoch die Hinweise nochmal getestet. Leider ohne Erfolg:

    Uc ist direkt von UserControl abgeleitet und wird ohne Reflections (z.B. Activator) instanziiert. Es liegen keine zusätlzichen DLLs oder PDBs im Ausführungsverzeichnis.

    Der Fehler tritt auch auf, wenn Uc keinen Inhalt hat, in Kleinbuchstaben umbenannt wird oder die Projektmappe auf .Net 4.5 umgestellt wird.

    Freitag, 3. Mai 2013 10:16
  • Hallo, hast du es mal mit der Assembly.LoadFile-Methode perobiert? Denn das funktioniert bei mir:

    Form1.vb

    Imports System.Reflection
    Imports System.Windows
    
    Public Class Form1
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim testAssembly As Assembly = Assembly.LoadFile("C:\Users\Tom\SkyDrive\Programmieren\WindowsApplication33\ClassLibrary1\bin\Debug\ClassLibrary1.dll")
    
            Dim calcType As Type = testAssembly.GetType("ClassLibrary1.UserControl1")
    
            ElementHost1.Child = CType(Activator.CreateInstance(calcType), UIElement)
        End Sub
    End Class
    UserControl1.xaml
    <UserControl x:Class="UserControl1"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 mc:Ignorable="d" Background="Yellow" d:DesignHeight="300" d:DesignWidth="300"/>




    <Code:13/> - Koopakiller [kuːpakɪllɐ]
    Webseite | Code Beispiele | Facebook | Snippets
    Wenn die Frage beantwortet ist, dann markiert die hilfreichsten Beiträge als Antwort und bewertet die Beiträge. Danke.
    Einen Konverter zwischen C# und VB.NET Code gibt es hier.

    Freitag, 3. Mai 2013 12:19
    Moderator
  • Hallo Koopakiller,

    ich habe ein entsprechendes Testprojekt erstellt und deinen Code aus Button1_Click eingefügt. Wie erwartet funktioniert es. Um den Fehler zu erzeugen, habe ich folgendes ergänzt.
      - leere Klasse "KlasseA" in 1. Klassenbibliothek DllA,
      - eine 2. Klassenbibliothek "DllB" mit Verweis auf DllA,
      - leere Klasse "KlasseB" in DllB abgeleitet von KlasseA.

    Vor Aufruf des UserControls wird KlasseB wie folgt instanziiert:

    	Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    		Dim dllA As Reflection.Assembly = Reflection.Assembly.LoadFile(
    			"[...]DllA\bin\Debug\DllA.dll")
    		Dim dllB As Reflection.Assembly = Reflection.Assembly.LoadFile(
    			"[...]DllB\bin\Debug\DllB.dll")
    		Dim kbType As Type = dllB.GetType("DllB.KlasseB")
    		Dim kb As Object = Activator.CreateInstance(kbType)
    
    		Dim ucType As Type = dllA.GetType("DllA.Uc")
    
    		ElementHost1.Child = CType(Activator.CreateInstance(ucType), Windows.UIElement)
    	End Sub

    Damit DllB DllA findet musste ich eine Kopie von DllA.dll in das Ausführungsverzeichnis kopieren. Nun erhalte ich wieder die Ausnahme "...verfügt nicht über eine Ressource...".

    AppDomain.CurrentDomain.GetAssemblies zeigt, dass DllA zweimal geladen wurde. Das wird verhindert, wenn LoadFile auf die Kopie von DllA.dll im Ausführungsverzeichnis angewendet wird.

    Ich habe gerade getestet, dass in meinem Anwendungsfall DllA auch zweimal geladen wird. Die DLLs liegen jedoch nicht als Datei, sondern als Byte-Stream vor. Die Lösung liegt vermutlich darin, dass DllB beim Laden die bereits geladene DllA findet und nicht ein zweites Mal lädt. Wie kann das erreicht werden?




    • Bearbeitet drh1 Dienstag, 7. Mai 2013 12:13 Schreibfehler
    Freitag, 3. Mai 2013 15:11
  • Ok, ich weiß was du meinst, ich denke mal das der Fehler darann liegt, wie die anderen Assemblies geladen werden. Die Anwendung lädt DllB aus dem Ordner DllB\bin\Debug. Die DllB braucht aber DllA und erwartet das DllA bei DllB liegt. Nun ist aber das Anwendungsverzeichnis der Ordner Main, wo DllA nicht gefunden wurde. Es  liegt also an DllB und nicht an der Anwendung selbst.

    Es gibt jedoch einen Weg um das laden der Assemblies ggf. anzupassen:

        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    
            'Wenn Assemblies geladen werden, dieses laden beeinflussen
            AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf CurrentDomain_AssemblyResolve
    
            Dim dllA As Reflection.Assembly = Reflection.Assembly.LoadFile("C:\Users\Tom\SkyDrive\Programmieren\Solution1\DllA\bin\Debug\DllA.dll")
            Dim dllB As Reflection.Assembly = Reflection.Assembly.LoadFile("C:\Users\Tom\SkyDrive\Programmieren\Solution1\DllB\bin\Debug\DllB.dll")
    
            Dim kbType As Type = dllB.GetType("DllB.KlasseB")
            Dim kb As Object = Activator.CreateInstance(kbType)
    
            Dim ucType As Type = dllA.GetType("DllA.Uc")
    
            ElementHost1.Child = CType(Activator.CreateInstance(ucType), Windows.UIElement)
    
        End Sub
    
        Private Shared Function CurrentDomain_AssemblyResolve(sender As Object, e As ResolveEventArgs) As System.Reflection.Assembly
    
            If e.Name = "DllA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" Then
                'DllA von Hand laden
                Return Assembly.LoadFile("C:\Users\Tom\SkyDrive\Programmieren\Solution1\DllA\bin\Debug\DllA.dll")
            End If
    
            'Andere DLL's ignorieren
            Return Nothing
    
        End Function
    Wenn versucht wird DllA zu laden, dann wird der neue Pfad angegeben.


    <Code:13/> - Koopakiller [kuːpakɪllɐ]
    Webseite | Code Beispiele | Facebook | Snippets
    Wenn die Frage beantwortet ist, dann markiert die hilfreichsten Beiträge als Antwort und bewertet die Beiträge. Danke.
    Einen Konverter zwischen C# und VB.NET Code gibt es hier.

    Freitag, 3. Mai 2013 15:45
    Moderator
  • Danke für die Antwort. In meinem Anwendungsfall wird System.Reflection.Assembly.Load(Byte[]) über das Ereignis AppDomain.CurrentDomain.AssemblyResolve aufgerufen. Alle Klassenbibliotheken werden also gefunden.

    Wie am 3.5.2013 15:11 geschrieben, liegt das Problem wahrscheinlich in dem doppelten Laden einer DLL. Ich vermute folgendes:

    DllA wird dynamisch geladen. Dann wird DllB dynamisch geladen und sucht die erforderliche DllA. DllB erkennt nicht, dass die erforderliche DllA identisch mit der bereits geladenen DllA ist und lädt DllA ein zweites Mal. Bei Instanziierung von Uc findet LoadComponent (siehe 1. Beitrag) die Resource DllA.Uc zweimal und wirft eine Ausnahme.

    Wie kann ich erreichen, dass DllB die bereits geladene DllA findet und verwendet?

    Montag, 6. Mai 2013 08:38
  • Ich habe nochmal mein Beispielprojekt überarbeitet. Ich lade die Assemblies nach wie vor am Anfang der Methode, die geladenen Assemblies werden dann aber als Shared Objekte in der Klasse gespeichert. Wenn nun eine Assembly eine Referenzierte Assembly lädt, dann bekommt Sie die Assembly, die schon geladen wurde:
        Shared dllA As Assembly
        Shared dllB As Assembly
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    
            'Wenn Assemblies geladen werden, dieses laden beeinflussen
            AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf CurrentDomain_AssemblyResolve
    
            dllA = Reflection.Assembly.LoadFile("C:\Users\Tom\SkyDrive\Programmieren\Solution1\DllA\bin\Debug\DllA.dll")
            dllB = Reflection.Assembly.LoadFile("C:\Users\Tom\SkyDrive\Programmieren\Solution1\DllB\bin\Debug\DllB.dll")
    
            Dim kbType As Type = dllB.GetType("DllB.KlasseB")
            Dim kb As Object = Activator.CreateInstance(kbType)
    
            Dim ucType As Type = dllA.GetType("DllA.Uc")
    
            ElementHost1.Child = CType(Activator.CreateInstance(ucType), Windows.UIElement)
    
        End Sub
    
        Private Shared Function CurrentDomain_AssemblyResolve(sender As Object, e As ResolveEventArgs) As System.Reflection.Assembly
    
            If e.Name = dllA.FullName Then
                'DllA von Hand laden
                Return dllA
            End If
    
            'Andere DLL's ignorieren
            Return Nothing
    
        End Function
    Wichtig ist nun die korrekte reihenfolge des Ladens der DLL. Bei mir funktioniert alles Tadellos. Wenn es nicht so sein sollte. Hier ist die gesamte Projektmappe: http://sdrv.ms/18NxUtm
    Und poste ggf. nochmal was konkret du anders gemacht hast.

    <Code:13/> - Koopakiller [kuːpakɪllɐ]
    Webseite | Code Beispiele | Facebook | Snippets
    Wenn die Frage beantwortet ist, dann markiert die hilfreichsten Beiträge als Antwort und bewertet die Beiträge. Danke.
    Einen Konverter zwischen C# und VB.NET Code gibt es hier.

    • Als Antwort markiert drh1 Dienstag, 7. Mai 2013 12:09
    Montag, 6. Mai 2013 16:19
    Moderator
  • Vielen Dank Koopakiller! Mein Anwendungsfall läuft jetzt.

    Die entscheidende Idee ist, die Assemblies Shared zu speichern.

    Es ist zwar noch nicht geklärt, warum bereits geladene Assemblies nicht gefunden werden und das Ereignis AppDomain.CurrentDomain.AssemblyResolve ausgelöst wird. Jedoch funktioniert ein Workaround mit einem Dictionary:

        AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf AssemblyLaden
    
        Public Shared Function AssemblyLaden(sender As Object, e As ResolveEventArgs) _
            As Reflection.Assembly
    
            Static dict As New Dictionary(Of String, Reflection.Assembly)
    
            Dim stammname As String = (New Reflection.AssemblyName(e.Name)).Name
            Dim resourceName As String = "Namespace." & stammname & ".dll"
    
            If Not dict.ContainsKey(resourceName) Then
                Using stream As IO.Stream = Reflection.Assembly.GetExecutingAssembly() _
                    .GetManifestResourceStream(resourceName)
    
                    If stream IsNot Nothing Then
                        Dim assemblyData(CInt(stream.Length)) As Byte
                        stream.Read(assemblyData, 0, assemblyData.Length)
                        dict(resourceName) = Reflection.Assembly.Load(assemblyData)
                    End If
                End Using
            End If
    
            If dict.ContainsKey(resourceName) Then Return dict(resourceName)
    
            Return Nothing
        End Function

    Gruß Volker


    • Als Antwort markiert drh1 Dienstag, 7. Mai 2013 12:09
    • Bearbeitet drh1 Dienstag, 7. Mai 2013 13:12 Link ergänzt
    Dienstag, 7. Mai 2013 12:06