C# DLL Dynamisch laden (WPF)

Ghost_Rider_R

Lieutenant
Registriert
Nov. 2009
Beiträge
787
Guten Tag zusammen,

ich bette meine externen Bibliotheken gerne als Resource ein und lade Sie dann dynamisch, damit ich eine Standalone-EXE habe.
Leider mag mir das mit dieser DLL (Microsoft.Toolkit.Win32.UI.Controls.dll) nicht gelingen, hat jemand eine Idee was ich falsch mache?

Unbenannt.PNG


Sobald ich die lokale Kopie abschalte d.h. auf false setzte, wird die Ausnahme geworfen, die DLL müsste doch aber durch die vorherige Zeile dynamisch geladen worden sein und bekannt sein oder?

jo.PNG



Vielen Dank für Eure Hilfe.

LG Ruff
 
Schau mal was der Rückgabewert von Assembly.Load ist, da sollte ja eine Referenz auf die Assembly zurückkommen wenn alles erfolgreich war (das in deinen Ressourcen ist schon ein Byte-Array mit dem Image und nicht der Dateiname als String? Wobei die Fehlermeldung dann eigentlich schon beim Load kommen müsste...)
 
aber ist es nicht so, dass ich durch Assembly.Load ihm genau diese fehlende DLL bereitstelle und Ihm sage hier ist die Bibliothek, wenn du sie suchst? Ich will ja eben nicht, dass diese irgendwo liegen muss sondern Sie in die Anwendung als Resource einbetten. Das Load läuft übrigens fehlerfrei durch. Ich glaube er schnallt nicht, dass die DLL die ich in Assembly.Load lade die ist, die er eigentlich sucht. Aber das übersteigt meine Kenntnisse.
 
naja, was heißt fehlerfrei... hast du dir mal angeschaut was es liefert? Ich hab in der Doku jetzt keine Exceptions gesehen für den Fall das ein Laden wegen fehlender Abhängigkeiten oder ähnlichem fehlschlagen sollte, nur für den Fall das es gar kein gültiges Assembly-Image wäre.
 
Schau dir mal die Properties der Exception an. Vlt gibt es sogar eine inner Exception?
Etwas mehr Informationen wären hilfreich.
 
Das ist die Exception die geworfen wird, sobald ich ein Objekt der Klasse MainWindow erstellen will, welches ein Control aus der externen DLL beinhaltet:

Die Datei oder Assembly "Microsoft.Toolkit.Win32.UI.Controls, Version=4.0.0.0, Culture=neutral, PublicKeyToken=4aff67a105548ee2" oder eine Abhängigkeit davon wurde nicht gefunden. Das System kann die angegebene Datei nicht finden.

Fehler.PNG


Eine Inner Exception gibt es hier nicht. Hat jemand ne Idee?
 
Lade doch vorher testhalber mal eine andere Ressource und verwende die darin enthaltene Assembly.
Geht das?

Verwendest Du das richtige Framework?

Schaue mal hier: https://www.nuget.org/packages/Microsoft.Toolkit.Win32.UI.Controls/

Da steht auch etwas von System.Net.Http als Dependency

Dependencies

.NETFramework 4.6.2
System.Net.Http (>= 4.0.0)
 
Implementiere das Event AssemblyResolve in der aktuellen AppDomain und lade die Assembly von Hand aus dem korrekten Pfad raus - wo auch immer die liegen mag.
Alle Assemblies die nicht gefunden wurden, landen automatisch in dieser Funktion und damit kannst du Abhängigkeiten selbst auflösen.

Ich nutze das schon erfolgreich seit Jahren für C++/CLI Bibliotheken im Geschäft.
Hier eine kleine Klasse die Anhand von Suchpfaden auflöst (Sorry ist C++, aber ist trivial dass nach C# zu portieren):

C#:
AssemblyResolver::AssemblyResolver(const char *searchPath, const char * dependencies[], int numDeps) {
    this->searchPath = gcnew System::String(searchPath, 0, strlen(searchPath));
    this->dependencies = gcnew array<System::String ^>(numDeps);
    for (int i = 0; i < numDeps; ++i) {
        this->dependencies[i] = gcnew System::String(dependencies[i], 0, strlen(dependencies[i]));
    }
    auto currentDomain = System::AppDomain::CurrentDomain;
    currentDomain->AssemblyResolve += (handler = gcnew System::ResolveEventHandler(this, &AssemblyResolver::OnAssemblyResolve));
}

AssemblyResolver::~AssemblyResolver() {
    auto currentDomain = System::AppDomain::CurrentDomain;
    currentDomain->AssemblyResolve -= handler;
    for (int i = 0; i < dependencies->Length; ++i) {
        delete dependencies[i];
    }
    delete dependencies;
    delete searchPath;
}

System::Reflection::Assembly ^ AssemblyResolver::OnAssemblyResolve(System::Object ^sender, System::ResolveEventArgs ^args) {
    System::Diagnostics::Debug::WriteLine(System::String::Format("OnAssemblyResolve: {0}", args->Name));

    auto assemblyName = args->Name;
    auto exeAssembly = System::Reflection::Assembly::GetExecutingAssembly();
    auto pathToAssembly = System::IO::Path::GetDirectoryName(exeAssembly->Location);

    auto searchPaths = gcnew array<System::String ^>(2);
    searchPaths[0] = pathToAssembly;
    searchPaths[1] = searchPath;

    System::Reflection::Assembly ^ result = nullptr;
    for (int i = 0; i < dependencies->Length; ++i) {
        System::String ^testDepAssemblyName = dependencies[i] + ",";
        if (assemblyName->Substring(0, testDepAssemblyName->Length)->Equals(testDepAssemblyName)) {
            for (int searchPathIndex = 0; searchPathIndex < searchPaths->Length; ++searchPathIndex) {
                try {
                    System::String ^newFullPath = System::IO::Path::Combine(searchPaths[searchPathIndex], dependencies[i] + ".dll");
                    System::Reflection::Assembly ^assembly = System::Reflection::Assembly::LoadFile(newFullPath);
                    result = assembly;
                    break;
                } catch (...) {
                }
            }
        }
        if (result) {
            break;
        }
    }

    delete searchPaths;

    return result;
}
 
Ich habe hier schonmal gepostet wie ich das gelöst habe.
Den Part mit "Du musst den Code nur so modifizieren ..." kannst Du Dir sparen, der Code lädt ja eine DLL aus einer Resource so wie Du es auch willst.
 
Danke erstmal für eure Antworten. Ich kann andere Assemblys laden, habe das Problem auch, obwohl ich in der selben AppDomain bleibe und würde es wenn möglich gerne vermeiden die DLL erst auf die Platte zu schreiben, behalte es aber als Alternative im Hinterkopf, vielen Dank dafür!).

Ich bin glaube ich aber einen Schritt weiter. Er findet die DLL die ich brauche, er findet auch dessen Abhängigkeit (System.Net.Http), was er aber nicht findet ist glaube ich eine Abhängigkeit einer Abhängigkeit:
Abhängigkeit.PNG


Das kommt als Ausnahme:
Exception.PNG


Der Verweis sagt mir leider nichts hat jemand eine Idee?

Danke für eure Hilfe!
 
In DepencyWalker kannst Du sehen welche DLL's die Microsoft.Toolkit.Win32.UI.Controls.dll verwendet (und es sind nicht wenige .. :-/)

Edit:
Vielleicht einfach mal einen try .. catch-Block um AppDomain.CurrentDomain.Load(..) machen, die WindowsRuntime muss er ja wohl nicht laden ..

2nd Edit:
Läuft denn Dein Programm mit externen DLL's überhaupt ? Das sollte zuallererst sichergestellt sein.
 
Mit externen DLLs läuft es ja, es läuft auch mit dieser DLL. Es kommt erst dann zu dieser Ausnahme, wenn die DLL nicht mehr direkt bei der EXE liegt, sondern wie im Beispiel genannt mit Assemby.load(...) geladen wird, während Sie als Resource vorliegt.

Auch wenn ich alles lade was geht, findet er immer diese Windows bzw. WindowsRuntime nicht. Ich denke, dass es auch diese ist, die das Problem verursacht.
Fehler.PNG


Nur was ich nicht verstehe, es dürfte für Ihn doch keinen Unterschied machen, ob ich Ihm die DLL direkt zur Verfügung stelle oder via Assembly.Load nachlade oder bin ich hier auf dem Holzweg?

Wenn es für jemanden was bringt könnte ich das Projekt oder die DLL auch zur Verfügung stellen.

Vielen Dank für eure Hilfe.
 
Ruff_Ryders_R schrieb:
Wenn es für jemanden was bringt könnte ich das Projekt oder die DLL auch zur Verfügung stellen.
Ja kannst ja mal ein abgespecktes Projekt als Zip anhängen.

Ich habe Assembly.Load noch nie verwendet, aber die Beispiele die ich gesehen habe (wie auch how-to-load-assemblies-into-an-application-domain erzeugen immer einen Type, eine Method und müssen davon noch eine Instanz erzeugen bevor sie es aufrufen können ..

Bei meiner Methode über Disk/LoadLibrary wird die DLL "normal" geladen und vom Betriebssystem Instanziert.
So das auch direkte Zugriffe wie:
Code:
...
        [DllImport("BASSMOD.dll")]
        private static extern bool BASSMOD_Init(int device, uint freq, uint flags);
...
if (!BASSMOD_Init(-1, 44100, 0)) {
...
möglich sind.

Klar der umweg über Disk ist nicht so schön, aber Du kannst ja in einer Liste festhalten welche Temp-Files erzeugt wurden und die beim beenden löschen.
 
Na dann mach ich das doch mal. Anbei der Quellcode. Sobald die Lokale Kopie der DLL auf true gesetzt wird läuft es einwandfrei. Falls wir das nicht hinbekommen wäre es super, wenn du mir das mit dem normal laden nochmal genauer erklären könntest. BASSMOD hab ich bis jetzt noch nie gehört, ist das zum laden von externen DLLs?

Danke für eure Hilfe!
 

Anhänge

Ruff_Ryders_R schrieb:
BASSMOD hab ich bis jetzt noch nie gehört, ist das zum laden von externen DLLs?
Nein, Bassmod ist eine verkleinerte Version der Bass, die nur MOD-Files spielen kann (XM, IT, S3M, MOD, MTM, UMX). Sie stellen zwar eine Wrapper-DLL bereit (BassMOD.Net.dll), aber ich rufe die Funktionen per DllImport's direkt auf.
Sie war nur die Beispiel-DLL die ich in ein Programm eingebettet habe.

Mit dem Quellcode kann ich leider nicht direkt helfen, da ich mit Win7/Visual Studio 2015 arbeite und sich das auch nicht so schnell ändern wird. :-/

Edit: Ich hab mal testweise die .NET Portable Library Reference Assemblies 4.6 runtergeladen, da ist gar keine System.Runtime.WindowsRuntime.dll enthalten ..
Ich hab auch Extra das SDK .NET Framework 4.6.2 installiert was bei deinem Projekt als Target Framework eingestellt ist, auch das hat keine System.Runtime.WindowsRuntime.dll !?!

Probier mal diese App.xaml.cs:
C#:
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Forms;

namespace Browser {
    public partial class App : System.Windows.Application {
        protected override void OnStartup(StartupEventArgs parameter) {
            LoadLib("Microsoft.Toolkit.Win32.UI.Controls.dll", Browser.Properties.Resources.Microsoft_Toolkit_Win32_UI_Controls);
        }

        protected override void OnExit(ExitEventArgs e) {
            String dirName = getDirName();
            if (Directory.Exists(dirName)) {
                try {
                    Directory.Delete(dirName, true);
                }
                catch (Exception) {
                }
            }

            base.OnExit(e);
        }

        [DllImport("kernel32", EntryPoint = "LoadLibrary", SetLastError = true, CharSet = CharSet.Unicode)]
        static extern IntPtr Kernel32LoadLibrary(string lpFileName);

        private static String getDirName() {
            return Path.Combine(Path.GetTempPath(), Assembly.GetExecutingAssembly().GetName().Name);
        }

        private static void LoadLib(String strDLLName, byte[] data) {
            String dirName = getDirName();
            if (!Directory.Exists(dirName))
                Directory.CreateDirectory(dirName);

            String dllPath = Path.Combine(dirName, strDLLName);
            using (Stream stm = new MemoryStream(data)) {
                try {
                    using (Stream outFile = File.Create(dllPath)) {
                        const int sz = 4096;
                        byte[] buf = new byte[sz];
                        while (true) {
                            int nRead = stm.Read(buf, 0, sz);
                            if (nRead < 1)
                                break;
                            outFile.Write(buf, 0, nRead);
                        }
                    }
                }
                catch {
                }
            }

            IntPtr h = Kernel32LoadLibrary(dllPath);
            Debug.Assert(h != IntPtr.Zero, "Unable to load library " + dllPath);
        }
    }
}
 
Zuletzt bearbeitet:
Hallo Lynxx,
vielen Dank für deine E-Mail. Ich habe das getestet, es läuft auch alles Fehlerfrei durch, nur sobald er dann die GUI erstellen will, kommt wieder die gleiche Ausnahme:

Fehler.PNG


Wie wenn er die DLL zwar lädt aber mit einem anderen Namen o.Ä. und diese dann doch nicht findet. Hat sonst noch jemand eine Idee, was ich machen könnte?
 
Wie vorher schon geschrieben: In DepencyWalker kannst Du sehen welche DLL's die Microsoft.Toolkit.Win32.UI.Controls.dll verwendet.
Du kannst auch Live sehen welche DLL's nachgeladen werden mit Sysinternals Process Monitor.
Diese DLL's musst Du eben auch als Resource hinzufügen und mit LoadLib(...) vor der Microsoft.Toolkit.Win32.UI.Controls.dll laden.
 
Ich habe mir das nochmal angesehen, das Problem ist wohl entgegen meiner ersten Aussage kein DLL spezifisches, sondern ein allgemeines Problem beim Laden einer DLL. Ich habe mal eine DLL erstellt, welche einfach nur zwei Zahlen addiert und diese zurück gibt. Es kommt aber genau der gleiche Fehler. Ich hatte das aber schon nur mit Assembly.Load gemacht und da hat es funktioniert, warum hier nicht? Es ist übrigens nur eine simple Konsolenanwendung als Beispiel :-)

Danke für eure Hilfe!
Ergänzung ()

Ich habe es noch einfacher gemacht. Diese Anwendung enthält zwei DLLs. Die erste kann er laden, bei der zweiten kommt ein Fehler, nur warum? Die Dlls sind beide gleich eingehängt und werden mit Assembly.Load geladen. Ich hoffe dann versteht Ihr mein Problem:

1539878721700.png
 

Anhänge

Zuletzt bearbeitet:
Ich habe das Problem nicht weil ich nur Funktionen anspreche über "[DllImport(...", wenn aber Objekte innerhalb der DLL direkt angesprochen werden muss die dll scheinbar zwingend im Pfad liegen, selbst wenn man einen Static Kontruktor anlegt und da die DLL wie zuvor beschrieben lädt, verweigert die Main-Funktion die arbeit.

Wenn man die Test aber so aufbaut:
C#:
using System;
using System.Reflection;

namespace test {
    class Program {
        static void Main(string[] args) {
            Assembly windll = Assembly.Load(test.Properties.Resources.Windows);
            Type myType = windll.GetType("Windows.Windows");
            MethodInfo myAddieren = myType.GetMethod("addieren");
            object windll_instanz = Activator.CreateInstance(myType);
            object[] parameter = { 3,5 };

            while (true) {
                Console.WriteLine(myAddieren.Invoke(windll_instanz, parameter));
                Console.ReadKey();
            }
        }
    }
}
funktioniert der aufruf von Windows.dll->addieren ..

Edit:
Die Microsoft.Office.Interop.Excel hat inkludierte Interop-Typen, deine selbstgemachte dll nicht, siehe auch:
Exemplarische Vorgehensweise: Einbetten von Typen aus verwalteten Assemblys in Visual Studio
 
Zuletzt bearbeitet:

Ähnliche Themen

Antworten
7
Aufrufe
5.334
Zurück
Oben