Schwer zu erklärendes Problem mit Invoke



  • Also in meiner Verwaltungs-DLL steht folgendes umd die Plugins zu laden:

    //...
    Assembly assembly = Assembly.LoadFrom(curFile);
                            Type[] assemblyTypes = assembly.GetTypes();
                            BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly;
    
                            //Klasseneinstiegspunkt suchen
                            for (int a = 0; a < assemblyTypes.Length; a++)
                            {
                                if (assemblyTypes[a].Name == "PRC_Interface")
                                {
                                    //Funktionierende Klasse gefunden -> neue Plugin-Klasse anlegen
                                    Plugin newPlugin = new Plugin();
    
                                    newPlugin.interfaceClassInstance = Activator.CreateInstance(assemblyTypes[a]);
                                    newPlugin.methods = assemblyTypes[a].GetMethods(bindingFlags);
    
    if (newPlugin.Initialize() != true)
                                        continue;
    //...
    

    Dies kommt in der Klasse für ein einzeilnes Plugin raus. Darin hab ich folgende Funktion, um einen Funktionsaufruf im Plugin zu machen:

    public object Invoke(string functionName, object[] parameters)
            {
                if (interfaceClassInstance == null || methods == null)
                    return null;
    
                for (int a = 0; a < methods.Length; a++)
                {
                    if (methods[a].Name == functionName)
                        return methods[a].Invoke(this.interfaceClassInstance, parameters);
                }
                return null;
            }
    

    Damit versuche ich folgende Funktion des Plugins aufzurufen (und zwar mit einem Parameter):

    public bool Initialize(Plugins.PluginManager pluginId)
            {
    

    Jedoch bekomme ich eine Exception sobald ich Invoke aufrufe.
    Wenn ich nun aus "PluginManager" object mache kann ich dazu kommen, dass die Funktion ohne Fehler aufgerufen wird - sobald ich jedoch zurück in "PluginManager" caste bekomme ich die Exception wie oben schon geschrieben.

    Danke

    Greetz :xmas1:



  • Manuel schrieb:

    Wenn ich nun aus "PluginManager" object mache kann ich dazu kommen, dass die Funktion ohne Fehler aufgerufen wird - sobald ich jedoch zurück in "PluginManager" caste bekomme ich die Exception wie oben schon geschrieben.

    Wie casten!? Wo tust Du hier casten? Casten ist in C# ganz schlecht... gewöhnt Dir lieber an "as" zu verwenden.

    Das Invoke geht aber bei mir ohne Probleme:

    using System;
    using System.Reflection;
    
    namespace ConsoleApplication1
    {
      public class A
      {
        public int method() { return 0; }
        public int methodWithArgs(string str) { return 0; }
      }
    
      class MyMethodInfo
      {
        public static int Main(string [] args)
        {
          A myA = new A();
          Type myTypea = Type.GetType("ConsoleApplication1.A");
          MethodInfo myMethodinfoA1 = myTypea.GetMethod("method");
          MethodInfo myMethodinfoA2 = myTypea.GetMethod("methodWithArgs");
    
          myMethodinfoA1.Invoke(myA, null);
          myMethodinfoA2.Invoke(myA, new object[] {"hello"} );
          return 0;
        }
      }
    }
    


  • Okay - das mit dem "as" merk ich mir 🙂

    So ists auch klar, dass das Invoke funktioniert. Mein Problem rührt ja auch daher, dass die Speicherverwaltung des Framework mich nicht von der dynamisch geladenen DLL in den Adressraum des Hauptprogramms lässt bzw. dass die PluginManager-Klasse im Plugin eine andere (zumindest für das Framework zur Laufzeit) ist als die im Hauptprogramm und die Typen somit nicht kompatibel sind.
    Aber irgendwie muss man diese Grenzen doch überwinden können - oder?

    Thx & Greetz :xmas1:



  • Das hab ich jetzt nicht ganz Verstanden, was Du da gesagt hast...

    Prinzipiell musst Du es doch so machen, wie es jemand ganz am Anfang gesagt hat:
    - Schnittstellen-DLL, welche alle gemeinsamen Klassen und Interfaces zwischen Plugin und App enthält.

    Und wenn dann sowohl die App als auch das Plugin genau die selbe Schnittstellen-DLL verwenden sollte es eigentlich keine Probleme geben.
    Anders geht es
    AFAIK nicht!



  • In der Common-Assembly:
    [csharp]
    /// <summary>
    /// Ermoeglicht die Erweiterung des Programmes zur Laufzeit
    /// durch von anderen Programmen her bekannten PlugIns.
    /// </summary>
    public interface IPlugin
    {
    /// <summary>
    /// Registriert das PlugIn in der Anwendung, was sich zum Beispiel durch
    /// das Erscheinen weiterer Menüpunkte auswirken kann.
    /// </summary>
    void Register();

    // Weitere Methoden, je nachdem was
    // deine PlugIns alles koennen sollen ..
    }
    [/csharp]

    In der Main-Assembly:
    [csharp]
    /// <summary>
    /// Überprüft ob in der angegebenen Assembly ein PlugIn für das Programm zu finden
    /// ist und fügt dieses gegebenenfalls zum PlugIn-Manager hinzu.
    /// </summary>
    /// <param name="assembly">Assembly mit möglichem PlugIn</param>
    public static void LoadPlugin(Assembly assembly) {
    // es werden einfach alle Typen in der Assembly durchlaufen
    foreach (Type type in assembly.GetTypes()) {
    if (type.GetInterface("IPlugin") != null) {
    try {
    PlugInManager.AddPlugin(Activator.CreateInstance(type));
    } catch(Exception ex) {
    // Ein Fehler trat beim Erstellen des PlugIns auf. Moegliche Gruende
    // fuer den Fehler sind z.B: type ist eine abstrakte Klasse, hat keinen
    // öffentlichen default-Konstruktor oder dieser hat eine Exception
    // geworfen
    }
    }
    }
    }
    [/csharp]

    Alles ungetestet

    Anschließend fragst du halt im PlugIn-Manager deine ganzen PlugIns ab und kannst
    eben alle Methoden, die du in der Schnittstelle IPlugin definiert hast verwenden.

    Hoffentlich ist dir damit der ungefähre Aufbau klar geworden .. :xmas1:



  • In der Common-Assembly:

    /// <summary>
    /// Ermoeglicht die Erweiterung des Programmes zur Laufzeit 
    /// durch von anderen Programmen her bekannten PlugIns.
    /// </summary>
    public interface IPlugin
    {
        /// <summary>
        /// Registriert das PlugIn in der Anwendung, was sich zum Beispiel durch
        /// das Erscheinen weiterer Menüpunkte auswirken kann.
        /// </summary>
        void Register();
    
        // Weitere Methoden, je nachdem was 
        // deine PlugIns alles koennen sollen ..
    }
    

    In der Main-Assembly:

    /// <summary>
    /// Überprüft ob in der angegebenen Assembly ein PlugIn für das Programm zu finden
    /// ist und fügt dieses gegebenenfalls zum PlugIn-Manager hinzu.
    /// </summary>
    /// <param name="assembly">Assembly mit möglichem PlugIn</param>
    public static void LoadPlugin(Assembly assembly) {
        // es werden einfach alle Typen in der Assembly durchlaufen
        foreach (Type type in assembly.GetTypes()) {
            if (type.GetInterface("IPlugin") != null) {
                try {
                    PlugInManager.AddPlugin(Activator.CreateInstance(type));
                } catch(Exception ex) {
                    // Ein Fehler trat beim Erstellen des PlugIns auf. Moegliche Gruende
                    // fuer den Fehler sind z.B: type ist eine abstrakte Klasse, hat keinen
                    // öffentlichen default-Konstruktor oder dieser hat eine Exception
                    // geworfen
                }
            }
        }
    }
    

    Alles ungetestet

    Anschließend fragst du halt im PlugIn-Manager deine ganzen PlugIns ab und kannst
    eben alle Methoden, die du in der Schnittstelle IPlugin definiert hast verwenden.

    Hoffentlich ist dir damit der ungefähre Aufbau klar geworden .. :xmas1:

    Sorry für Doppelpost, aber ich wollte es nochmal schön formatiert darstellen.



  • Jo. Genau so hab ich das auch schon die ganze Zeit am laufen. Dennoch - vielen Dank für deine Mühe 🙂

    Das Aufrufen von Funktionen innerhalb des Plugins von außen is ja auch kein Thema. Problematisch wird's, wenn das Plugin Funktionen bzw. Variablen in der Schnittstellen-DLL aufrufen möchte, die vom Hauptprogramm aus gesetzt worden (nicht bei Standard-Typen a la int, aber bei eigenen). Lässt sich schwer erklären, ich hoffe ihr wisst, was ich meine.

    Hauprogramm ---Aufruf---> Plugin-Funktion funktioniert super

    Plugin ---Zugriff---> Hauptprogramm/Schnittstellen-Funktion/Variable Exception

    Danke für eure viele Arbeit 🙂
    Greetz



  • Folgende, vielleicht zum Thema passende Situation sei gegeben:

    // Pseudo-MSIL
    .assembly Foo
    {
        .namespace Dll
        {
            .class public Type
            {
                .method public instance void Call() cil managed
                {
                    ret
                }
            }
        }
    }
    .assembly Bar
    {
        .namespace Dll
        {
            .class public Type
            {
                .method public instance void Call() cil managed
                {
                    ret
                }
            }
        }
    }
    

    Wenn du die Klasse Type im Namensraum Dll mit dem selben Code in zwei verschiedenen Assemblies kompilierst, sind sie nicht kompatibel, obwohl sie sich genau gleich verhalten und genau gleich aussehen. Beim Aufruf einer Funktion Call von Type aus dem Assembly 'Qux' (z. B. Foo oder Bar) wird in MSIL etwas derartiges generiert:

    call instance void [Qux] Dll.Type::Call()
    

    Nun werden aber bei .Net alle Dinge stark abgesichert, in diesem Fall wird geprüft, ob das Objekt, auf dem [Qux] Dll.Type::Call aufgerufen wird, wirklich vom Typ [Qux] Dll.Type ist. Falls es das nicht ist, dann wird eine Exception geworfen und das Programm beendet, was bei dir vermutlich der Fall ist.



  • Ist schon alles in einem Assembly definiert. Alle Sachen, die ich von Hauptprogramm und Plugin aus gleich verwenden möchte sind in der Schnittstellen-Dll definiert.

    Nun aber ... hmm ... ist es vielleicht möglich, dass das Framework zwei unterschiedliche Instanzen der Schnittstellen-DLL lädt (und deswegen Inkompatiblität feststellt), wenn folgende Situation vorherrscht:

    C:/Hauptprogramm.exe ---hat als Referenz--> C:/Interface.dll
    C:/Plugins/Plugin.dll ---hat als Referenz--> C:/Plugins/Interface.dll

    ... ? Sorry - daran hatte ich vorher nicht gedacht.

    Wenn ja, wie kann ich es schaffen, dass nur eine Instanz geladen wird? Oder muss ich dazu wirklich alle Assemblys in einem Ordner haben?

    Greetz :xmas1:



  • Hab die Plugins jetzt mit in den Ordner des Hauptprogramms gepackt .. und ... es funktioniert alles super. Danke für den Denkanstoß 🙂

    Wenn aber noch jemand ne Idee hat, wie ich das auch in verschiedenen Verzeichnissen zum Laufen bekomme, bin ich dafür offen 😉

    Danke Leuts.
    Greetz :xmas1:


Anmelden zum Antworten