Laden...

.Netz gepackte DLLs dynamisch laden - wie "Plugins"

Erstellt von andrgrau vor 14 Jahren Letzter Beitrag vor 11 Jahren 7.053 Views
A
andrgrau Themenstarter:in
22 Beiträge seit 2009
vor 14 Jahren
.Netz gepackte DLLs dynamisch laden - wie "Plugins"

Beschreibung:

Ich hatte ein Problem, das ich auch hier geschildert habe .Netz gepackte EXE: dll dynamisch laden. Ich hab mir nun dazu meine eigene Lösung gebaut 🙂
Kurz gesagt geht es darum, dass ich DLL Dateien ("Plugins") zusammen mit einer EXE mittels .Netz gepackt habe und die DLL Dateien dynamisch laden möchte. Dynamisch deswegen, weil man nicht weiß, ob und welche DLL Dateien mit gepackt wurden, also der Name unbekannt ist. Wenn DLL Dateien im selben Ordner liegen, kann man den Ordner danach durchsuchen, aber wenn sie mit .Netz gepackt wurden, dann geht das ja nicht. .Netz packt die einzelnen Assemblies als Resource in die EXE mit rein, so dass man gar nicht so einfach darauf zugreifen kann, wie man das gerne hätte. Schaut man sich die EXE mit dem Reflector an, so gibt es nur eine Klasse - den NetzStarter.
Es ist darauf zu achten, dass .Netz mit dem Parameter "-d" für jede DLL aufgerufen wird, die so geladen werden soll.

Meine Vorgehenweise, mit der ich es gelöst habe, ist dabei die folgende:1.Es wird überprüft, ob es sich überhaupt um eine EXE handelt, die mit .Netz gepackt wurde, da wir uns ja keine unnötge Arbeit machen wollen 😉

1.Wenn die EXE mit .Netz gepackt wurde, holen wir uns einen Stream auf die Resource des "EntryAssembly's", mit dem wir dann die Resource lesen können. 1.Wir holen uns die Methode "GetAssemblyByName" vom NetzStarter. Diese Methode gibt uns die Assembly zurück, die zu einem bestimmten Namen gehört, also aus den Resourcen und auch gleich entpackt (unzipped). 1.Wir iterieren über jede Resource, rufen die GetAssemblyByName Methode dabei für jeden Namen auf und erhalten dann ein Objekt vom Typ Assembly. 1.Es wird überprüft, ob es sich um eine Assembly handelt, die wir auch wirklich haben wollen. Dabei wird geprüft, ob es einen Type in dem Assembly gibt, der von einem bestimmten Interface erbt. Wenn dies der Fall ist, holen wir uns eine Instanz von dem Interface bzw. der erbenden Klasse aus dem Assembly und fügen es einer Liste hinzu. 1.Nach Durchlauf von allen Assemblies geben wir die gesamte Liste zurück.

Im angehängten Projekt ist meine gesamte Klasse enthalten, die sowohl nach DLL Dateien im gleichen Ordner sucht und in der EXE, falls sie mit .Netz gepackt wurde.

Anmerkung:
Das ist das erste, was ich hier poste, ich habe es nach meinem besten Wissen gemacht und hoffe, dass es der eine oder andere verwenden kann. Ich bin für jedes Feedback offen, sowohl Lob als auch Kritik, das gleiche gilt natürlch auch für Fragen zum Quellcode 🙂

Quellen/Anregungen:*Using NetZ with Dynamically Loaded Assemblies von Marc Clifton *Plug-ins in C# von Redth *.Netz selber natürlich 😉 *Reflector


        /// <summary>
        /// Searches in the assembly's resources for possible Plugins which a included with the -d option from .Netz.
        /// (If the exe itself was not packed with .Netz, the resources will not be scanned.)
        /// </summary>
        /// <param name="listPlugins">List of all IPlugins. (New ones to be added to.)</param>
        static void SearchNetzPlugins(List<IPlugin> listPlugins)
        {
            // Get the FriendlyName and replace .vshost with an empty String.
            string appName = AppDomain.CurrentDomain.FriendlyName;
            appName = appName.Replace(".vshost", String.Empty); // -> .vshost is used for debugging and leads to a failure when calling Type.GetType!

            // Cut everything behind the last ".", e.g. ".exe"
            int pos = appName.LastIndexOf(".");
            if (pos != -1)
                appName = appName.Substring(0, pos);

            Type type = null;
            try
            {
                // Now we're able to (try to) get the "netz.NetzStarter" Type
                type = Type.GetType("netz.NetzStarter, " + appName);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.StackTrace);
                return;
            }


            // type will be not null if the application was started from the netzloader
            if (type != null)
            {
                Stream stream = null;
                try
                {
                    // Get resource's stream from the entry assembly (which is the netzloader)
                    stream = Assembly.GetEntryAssembly().GetManifestResourceStream("app.resources");
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.StackTrace);
                    return;
                }

                // If there is a stream
                if (stream != null)
                {
                    // Get the "GetAssemblyByName" method from .Netz which can be called later to get an assembly out of the resources
                    MethodInfo getAssemblyByNameMethod = type.GetMethod("GetAssemblyByName", BindingFlags.NonPublic | BindingFlags.Static);

                    // Create a resource reader to read from the stream
                    ResourceReader resReader = new ResourceReader(stream);
                    // Get the enum of the resources
                    IDictionaryEnumerator resEnum = resReader.GetEnumerator();

                    // Iterate over every resource's enum
                    while (resEnum.MoveNext())
                    {
                        // zip.dll must be excluded as it can't be unpacked
                        if (resEnum.Key.ToString().Equals("zip.dll"))
                            continue;

                        Assembly pluginAssembly = null;
                        try
                        {
                            // Invoke the "GetAssemblyByName" method from netz and give it the resource's key as parameter
                            pluginAssembly = (Assembly)getAssemblyByNameMethod.Invoke(null, new object[] { resEnum.Key.ToString() });
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine(ex.StackTrace);
                            continue;
                        }

                        // If there is an assembly
                        if (pluginAssembly != null)
                        {
                            // Get an instance of the Interface (if it's a Plugin)
                            IPlugin plugin = IsPluginAssembly(pluginAssembly);

                            // If it's a Plugin then add it to the list of all possible Plugins
                            if (plugin != null)
                            {
                                listPlugins.Add(plugin);
                            }
                        }
                    }
                }
            }
        }

Schlagwörter: .Netz Dynamically Loaded Assemblies Assembly Plugin DLL Resource NetzLoader

T
1 Beiträge seit 2012
vor 11 Jahren

Hallo andrgrau,

auf Deiner Basis habe ich einen Unpacker für den Netzstarter erstellt.



namespace NetzStarterUnpacker
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO;
    using System.Reflection;
    using System.Collections;
    using System.Resources;
    using ICSharpCode.SharpZipLib.Zip.Compression.Streams;

    public class Unpacker
    {
        public static void UnpackResources(string folder, string appName)
        {
            Assembly mainAssembly = Assembly.LoadFrom(Path.Combine(folder, appName));
            Stream stream = mainAssembly.GetManifestResourceStream("app.resources");

            // If there is a stream
            if (stream != null)
            {
                Type type = mainAssembly.GetTypes().FirstOrDefault(x => x.Name == "NetzStarter");

                // Get the "GetResource" method from .Netz which can be called later to get the resources
                MethodInfo getResourceMethod = type.GetMethod("GetResource", BindingFlags.NonPublic | BindingFlags.Static);

                // Create a resource reader to read from the stream
                ResourceReader resReader = new ResourceReader(stream);
                // Get the enum of the resources
                IDictionaryEnumerator resEnum = resReader.GetEnumerator();

                // Iterate over every resource's enum
                while (resEnum.MoveNext())
                {
                    // zip.dll must be excluded as it is not needed
                    if (resEnum.Key.ToString().Equals("zip.dll"))
                        continue;

                    try
                    {
                        byte[] resource = (byte[])getResourceMethod.Invoke(null, new object[] { resEnum.Key.ToString() });
                        if (resource == null)
                        {
                            throw new Exception("application data cannot be found");
                        }

                        int index = resEnum.Key.ToString().IndexOf("!2");
                        string assemblyName = resEnum.Key.ToString().Substring(0, index) + ".dll";

                        MemoryStream outStream = UnZip(resource);
                        File.WriteAllBytes(Path.Combine(folder, assemblyName), outStream.ToArray());
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.StackTrace);
                        continue;
                    }
                }
            }
        }

        private static MemoryStream UnZip(byte[] data)
        {
            if (data == null)
            {
                return null;
            }
            MemoryStream stream = null;
            MemoryStream stream2 = null;
            InflaterInputStream stream3 = null;
            try
            {
                stream = new MemoryStream(data);
                stream2 = new MemoryStream();
                stream3 = new InflaterInputStream(stream);
                byte[] buffer = new byte[data.Length];
                while (true)
                {
                    int count = stream3.Read(buffer, 0, buffer.Length);
                    if (count <= 0)
                    {
                        break;
                    }
                    stream2.Write(buffer, 0, count);
                }
                stream2.Flush();
                stream2.Seek(0L, SeekOrigin.Begin);
            }
            finally
            {
                if (stream != null)
                {
                    stream.Close();
                }
                if (stream3 != null)
                {
                    stream3.Close();
                }
                stream = null;
                stream3 = null;
            }
            return stream2;
        }

        private static string UnMangleDllName(string dll)
        {
            return dll.Replace("!1", " ")
                        .Replace("!2", ",")
                        .Replace("!3", ".Resources")
                        .Replace("!3", ".resources")
                        .Replace("!4", "Culture");
        }
    }
}

A
andrgrau Themenstarter:in
22 Beiträge seit 2009
vor 11 Jahren

Ahoi tla75,

krass, das ganze ist über 3 Jahre alt und ich habe weder damit noch mit C# fast 3 Jahre nichts mehr gemacht! Freut mich, dass du meinen Code weiterverwenden konntest, dann war es doch nicht ganz umsonst, dass ich es damals hier gepostet habe!

Viele Grüße
andrgrau