Laden...

Funktionen mit Parametern aus fremder DLL aufrufen

Erstellt von e86 vor 18 Jahren Letzter Beitrag vor 18 Jahren 3.465 Views
E
e86 Themenstarter:in
56 Beiträge seit 2004
vor 18 Jahren
Funktionen mit Parametern aus fremder DLL aufrufen

Hallo,

ich möchte während der Laufzeit des Programms eine DLL einbinden, die ich selbst entworfen habe. Diese enthält Funktionen, welchen in verschiedene Klassen gekapselt sind.

Nun möchte ich wissen, wie ich diese Funktionen ausrufen kann. Wichtig ist, dass ich an diese Funktionen noch Parameter übergeben muss.

Meine Lösungsansätze sind bisher:

  • .. über:
System.Reflection.Assembly.LoadFile ("deineDLL.dll");
  • ... oder über:
[Dll Import ... ]

Bei zweiterem habe ich keine Ahnung, wie da auf die Klasse zugreifen kann, da er mir, so glaube ich, die ganze DLL einfügt.

Ich hoffe, ihr versteht so in etwa, was ich meine =)

kOOni

L
667 Beiträge seit 2004
vor 18 Jahren

Hallo !

Schau Dir mal das Factory-Muster an.
Definiere auf der Seite der aufrufenden DLL ein Interface für den Typ in der aufgerufenen DLL und implementiere das Interface dann.

Nun kannst Du mit einer Factory problemlos Objekte aus Deiner dynamisch geladenen DLL verwenden. Hier noch etwas Sourcecode zum Denkanstoss :


/// <summary>
		/// Tries to create a new instance of the given type, searching it in the given assembly.
		/// </summary>
		/// <param name="aioType">The name of the instance type, implementing the IAnalogIO interface.</param>
		/// <param name="assembly">The name of the assembly, the type is located in.</param>
		/// <returns>An instance of the given type from within the given assembly.</returns>
		/// <list>
		/// <listheader>
		/// <term>
		/// Possible Exceptions
		/// </term>
		/// </listheader>
		/// <item>
		/// <term>
		/// InitializationException
		/// </term>
		/// <description>
		/// Thrown if the specified type either was not found in the specified assembly or if the given types is an abstract type.
		/// </description>
		/// </item>
		/// </list>
		internal IJoystick CreateJoystick(string joystickType, string assembly)
		{
			Hashtable types = new Hashtable();
			Assembly ass = Assembly.Load(assembly);

			foreach(Type t in ass.GetTypes())
			{
				if(typeof(IJoystick).IsAssignableFrom(t) && !t.IsAbstract)
					types.Add(t.Name, t);
			}

			if(types.Contains(joystickType))
			{
				IJoystick joy = (IJoystick)Activator.CreateInstance((Type)types[joystickType]);	
				return joy;
			}
			else
				throw new InitializationException("Error initializing peripherals : The Joystick-type " + joystickType + " is not defined in Assembly " + assembly + " !!"); 
		}

Welche Typen aus welcher Assembly beim Initialisieren der Anwendung geladen werden sollen kannst Du dann z.B. per Config-File bestimmen.

Vielleicht hilfts ja.

Grüße,
Lynix

"It is not wise to be wise" - Sun Tzu

F
10.010 Beiträge seit 2004
vor 18 Jahren

Willst Du wirjklich erst zur laufzeit die DLL einbinden,
oder hast Du diese DLL erstellt und willst sie normal von deinem Programm aus benutzen?

Bei letzterem einfach die DLL unter Referenzen hinzufügen angeben.

DLLImport wird nur benutzt bei Nicht managed DLL's.

E
e86 Themenstarter:in
56 Beiträge seit 2004
vor 18 Jahren

Hallo,

nein, ich möchte die DLL erst zur Laufzeit einbinden. Sie ist in C# geschrieben.

kOOni

F
10.010 Beiträge seit 2004
vor 18 Jahren

Also du hast eine Dll, die Du nicht zur entwurfszeit benutzen willst?

Ist das ein Plugin oder warum willst Du sie nicht als referenz einfügen?

E
e86 Themenstarter:in
56 Beiträge seit 2004
vor 18 Jahren

Es ist ein Ordner, der bestimmte Module enthält. Diese Module stellen meinem Programm Funktionen zur Verfügung.

Es ist so, dass ich diese Module erst zur Laufzeit auslesen will, da ich nicht weiß, welche Module in dem Ordner drin sind. Würde ich sie per Referenz einfügen, dann könnte ich nur die dort eingetragenen Module verwenden, was ich aber nicht will.

Wenn du es so siehst, dann sind es ein oder mehrere Plugins. =)

Also du hast eine Dll, die Du nicht zur entwurfszeit benutzen willst?

Genau.

kOOni

830 Beiträge seit 2005
vor 18 Jahren

Hallo e86,

hier mal ein Ausschnitt aus meiner Methode, die sowas erledigt:


DirectoryInfo dirInfo = new DirectoryInfo(this.pluginDirectory);
FileInfo[] pluginFiles = dirInfo.GetFiles("*.dll");

if(pluginFiles.Length>0)
{
	foreach(FileInfo plugin in pluginFiles)
	{
		Assembly assembly = Assembly.LoadFrom(plugin.FullName);
		string assemblyName = assembly.GetName().ToString();
		string[] assemblyNameList = assemblyName.Split(',');
		assemblyName = assemblyNameList[0].Trim();
        Type assemblyType = assembly.GetType("Namespace."+assemblyName);					
					
		if(assemblyType != null)
		{
			object pluginObject = Activator.CreateInstance(assemblyType);
			YourObject baseObject = pluginObject as YourObject;
		}				
	}
}

Hoffe das hilft dir weiter.

Gruss
Friedel

Ohne Ziel ist auch der Weg egal.

E
e86 Themenstarter:in
56 Beiträge seit 2004
vor 18 Jahren

Hallo,

nun, soweit bin gestern nun auch gekommen, aber das Problem ist nun:

Wie greife ich bei diesem Objekt nun auf die jeweiligen Funktionen zu?

Bei dir Friedel wäre das:

baseObject.MeinFunktionsname (Parameter1, Parameter2);

Aber dann meckert das Visual Studio 2005 rum und meint, dass das "Object" keine solche Funktion besitzt. 🤔 Daran scheitere ich derzeitig, logisch funktioniert es, nur das Programm meint, dass es noch "Probleme" gibt.

kOOni

830 Beiträge seit 2005
vor 18 Jahren

Hallo e86,

Edit:
Nehme alles zurück und behaupte das Gegenteil.:

Den Cast hast du entsprechend durchgeführt und die Instanz baseObject hat jetzt den Typ, der die Methode MeinFunktionsname enthält.
D.h. wenn du dir eine MessageBox ausgeben lässt, die dir baseObject.GetType().ToString() ausgibt, steht da der Type YourObject, oder object ?

Steht da object, musst du pluginObject nach YourObject casten.
Steht da YourObject, dann hat dein Typ YourObject nicht die aufgerufene Methode, oder diese ist nicht als public deklariert.

Handelt es sich bei den Plugins nicht immer um den selben Typ C, dann musst du alle Plugins von einer Basisklasse B ableiten, oder ein Interface benutzen.

Gruss
Friedel

Ohne Ziel ist auch der Weg egal.

E
e86 Themenstarter:in
56 Beiträge seit 2004
vor 18 Jahren

Hallo,

erstmal entschuldigung, ich hatte mich mit dem Quellcode vertan. Weshalb du meinen letzten Post vergessen kannst.

Ich hatte es so ähnlich bewerkstelligt. Was meinst du mit Casten?

Zur Zeit habe ich kaum die Möglichkeit daran weiterzuarbeiten, aber ich würde das Problem trotzdem gern lösen.

Danke dir ersteinmal für deine Hilfe!

kOOni

E
e86 Themenstarter:in
56 Beiträge seit 2004
vor 18 Jahren

Hallo zusammmen,

danke erstmal für eure Hilfe, das hatte ich ganz vergessen!

Nun habe ich mich endlich mal hinsetzen können und habe etwas herumprobiert.

string pluginDirectory = "F:\\.. \\Release";

DirectoryInfo dirInfo = new DirectoryInfo(pluginDirectory);
FileInfo[] pluginFiles = dirInfo.GetFiles("*.dll";);

if(pluginFiles.Length>0)
{
foreach(FileInfo plugin in pluginFiles)
{
Assembly assembly = Assembly.LoadFrom(plugin.FullName);
string assemblyName = assembly.GetName().ToString();

Console.WriteLine (assemblyName);

string[] assemblyNameList = assemblyName.Split(',');
assemblyName = assemblyNameList[0].Trim();
Type assemblyType = assembly.GetType("Namespace."+assemblyName);

if(assemblyType != null)
{
object pluginObject = Activator.CreateInstance(assemblyType);
Console.WriteLine (pluginObject.GetType().ToString() );
}
else
{
Console.WriteLine ( "assemblyType = null!" );
}
}
}

Console.ReadLine ();

In dem Release-Ordner habe ich mal eine Testassembly eingerichtet. Führe ich diese Console-Anwendung nun aus, so kommt folgende Ausgabe:

Testassembly, Version = ...
assemblyType = null!

Nun habe ich noch eine Frage dazu:

Type assemblyType = assembly.GetType("Namespace."+assemblyName); 

Was bewirkt dies? Was meinst du mit Namespace. ?

Aso, was mir noch aufgefallen ist. Bei dieser Zeile hat er gleich abgebrochen:

 YourObject baseObject = pluginObject as YourObject;

Dabei habe ich statt YourObject meine Klasse "Class1" hereingemacht. Nichts. Geht nicht so..

Könntet ihr mir nicht ein kleines Beispiel-Projekt anlegen liebfrag =)

kOOni
ps.: Ich benutzte nun: .NET1.1 mit dem Visual Studio 2003 Academic

E
e86 Themenstarter:in
56 Beiträge seit 2004
vor 18 Jahren

Hallo,

ich habe nun eine Idee, was es sein könnte. Ich habe das schonmal ähnlich in C++ gesehen. Und ich glaube, dass meintest du auch mit "casten".

Und zwar benötige ich eine Art Technik, die einem eine Art anonyme Klasse als Daten bzw. Klassentyp zur Verfügung stellt. Dies nennt sich "Casting".

Mal kurz ein Beispiel:

public class Stack<T>
{
T[] items;
int count;
public void Push(T item) {...}
public T Pop() {...}
}

Ich glaube, dass ich dafür auch die Generics benötige (sind in .NET 2.0 implementiert)

Ich habe nun folgende Vorstellung:

  1. Ich lade die Assembly per
Assembly objAssembly=System.Reflection.Assembly.LoadFrom(/* pfad zur Assembly */);
  1. Dann suche ich die Klasse mit dem Namen "MyClass"

oder

  1. Ich lege mir nur eine Klasse an und lasse sie mir zurückgeben

System.Reflection.Assembly b = Reflection.Assembly.LoadFrom("meine_dll.dll");
Type type = b.GetTypes();

  1. Dann erstelle ich eine neue Instanz von ihr (der Klasse):

object a;
a = b.CreateInstance(types(0).FullName)

Nun kommt das Problem. Ich darf ihm hier keinen richtigen Typ zuweisen, wie "object" oder "string", sondern nur eine Art Prototyp. Dieser Prototyp ist dann ein PLatzhalter für die richtige Klasse, welche instanziert wird zur Laufzeit. Denn wenn ich ihm hier einen bekannten Typ zuweise, wie object, dann kann dieser Typ ja nicht meine Methoden enthalten, die ich erst einlesen will. Ich brauche also im Endeffekt einen Typ der jeden anderen Typ annehmen kann.
Ich finde, dass ist etwas schwer zu verstehen, aber ich hoffe, ihr könnt mir folgen. Ich habe soetwas schoneinmal bei C++ gesehen.

_Nachtrag:
Könnte sich auch in C++ um die Templates handeln. _

(Man möge mir für etwaige Fehler verzeihen, denn ich hab hier fremden VB.NET Code umgeschrieben. http://www.tutorials.de/tutorials202552.html)

Schönen Abend noch,

kOOni

4.221 Beiträge seit 2005
vor 18 Jahren

object ist schon richtig....

object ist die Mutter aller Kinder (aller Reference-Types)

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

830 Beiträge seit 2005
vor 18 Jahren

Hallo e86,

Nun habe ich noch eine Frage dazu:

C#-Code:

Type assemblyType = assembly.GetType("Namespace."+assemblyName);   

Was bewirkt dies? Was meinst du mit Namespace. ?

Diese Zeile ermittelt den Typ der geladenen Assembly. Der Namespace gibt hier an, welche Plugins geladen werden sollen. Nur dlls, die in einem bestimmten Namespace liegen, werden geladen (instanziiert). Dein Namespace der Class1 legst du ja selber fest. Somit weißt du nach welchen Namespaces du suchem musst.

Was den Aufbau mit Generics angeht. Eigentlich sind die für ein einfaches Plugin Verfahren nicht nötig.
Der Aufbau muss in etwa so sein:

                             BaseClass  
    AClass:BaseClass             BClass:BaseClass  

AClass und BClass werden von BaseClass abgeleitet. In der BaseClass legst du alle Methoden und Eigenschaften fest, die die ChildClasses (AClass,BClass) gemeinsam haben.
Im "PluginLoader" castest du das geladene Object in diese BasisClass. Somit hast du Zugriff auf die gemeinsamen Methoden und Eigenschaften. Der wirkliche Typ deines neuen BaseClass-Object ist aber nicht BaseClass, sondern z.B. AClass. Somit ist es möglich auf Methoden und Eigenschaften zuzugreifen, die nur AClass besitzt. Entweder indem du unterscheidest und nochmal castest (nur bei festgelegter Anzahl der zu erwartenden Typen (Plugins) zu empfehlen), oder du machst das per Reflection.
Die obige Vorgehensweise ist auch mit Schnittstellen zu realisieren. Was besser ist, muss du selbst entscheiden. Sollen deine Methoden, die für beide Klassen relevant sind, bereits Implementierung mitbringen, ist die Vorgehensweise mit Vererbung die Richtige.

Gruss
Friedel

Ohne Ziel ist auch der Weg egal.

S
8.746 Beiträge seit 2005
vor 18 Jahren

Bei PlugIns sollte man eigentlich immer mit Interfaces arbeiten, anstelle von Objekten. Die konkreten Objekte erzeugt man über ein Abstract Factory-Objekt (liefert nur Interface anstelle von Objekt). Die Factory erhält einen String als Parameter zur Wahl des konkreten Typs. Nur die Factory wird via CreateInstance erzeugt. So erreicht man maximale Entkopplung und Konfigurierbarkeit der PlugIns.

E
e86 Themenstarter:in
56 Beiträge seit 2004
vor 18 Jahren

Hallo,

danke ersteinmal für die Antworten!

Ich habe nun folgendes Problem:

Cannot implicitly convert type 'object' to 'eQual._Module'. An explicit conversion exists (are you missing a cast?)

Ich habe in der per


System.Reflection.Assembly AssemblyModuleMySQL;

AssemblyModuleMySQL = System.Reflection.Assembly.LoadFrom(Path + 
"\\eQ_ModuleMySQL.dll");

Type MyType = Type.GetType("eQ_ModuleMySQL.ModuleMySQL");

_Module MyObj = new _Module();

MyObj = Activator.CreateInstance(MyType);

Die dll-Datei eingebunden. Vorher noch die Deklaration der "ModuleMySQL" (sie befindet sich in der Dll "eQ_ModuleMySQL.dll")

public interface Module
    {                           
        void Connect();
        void Disconnect();
    }


    public class ModuleMySQL : Module 
    {
        public ModuleMySQL(string datasource, string databasename, string userid,
                                      string password)
        {

        }

        public void Connect ()
        {
        }

        public void Disconnect () 
        {
        }
    }

Er meint, dass ich vielleicht einen 'Cast' vergessen habe, aber ich weiss nicht, was er damit meint.

kOOni

4.221 Beiträge seit 2005
vor 18 Jahren

Offtopic:

Nur so als Tipp am Rande:

Interfaces immer mit einem I kennzeichnen

Also IModule statt nur Module

/Offtopic:

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

830 Beiträge seit 2005
vor 18 Jahren

Hallo e86,

MyObj = (MyObj)Activator.CreateInstance(MyType);

Activator.CreateInstance gibt ein object zurück, welches man explizit in ein MyObj casten ("wandeln") muss.

Gruss
Friedel

Ohne Ziel ist auch der Weg egal.

E
e86 Themenstarter:in
56 Beiträge seit 2004
vor 18 Jahren

@Programmierhans:

<offtopic>

Ok, mache ich. 🙂

</offtopic>

btT:


System.Reflection.Assembly AssemblyModuleMySQL;

AssemblyModuleMySQL = System.Reflection.Assembly.LoadFrom(Path + 
"\\eQ_ModuleMySQL.dll");

Type MyType = Type.GetType("eQ_ModuleMySQL.ModuleMySQL");

_Module MyObj = new _Module();

MyObj = (MyObj)Activator.CreateInstance(MyType);

Nun kommt folgende Fehlermeldung

The type or namespace name 'MyObj' could not be found (are you missing a using directive or an assembly reference?) F:..\eQual_ModuleManager.cs

in der Zeile mit dem (MyObj). Was ich dazu sagen muss, ich habe dies jetzt einfach dirty per Strg-C und Strg-V eingebaut... 🙂

Nachtrag:

Was macht eigentlich die neue Zeile, Friedel?

kOOni

830 Beiträge seit 2005
vor 18 Jahren

Ach, Blödsinn, ich meine

MyObj = (_Module)Activator.CreateInstance(MyType);

Wie gesagt, es "wandelt" ein Object in den Type _Module

Gruss
Friedel

Ohne Ziel ist auch der Weg egal.

E
e86 Themenstarter:in
56 Beiträge seit 2004
vor 18 Jahren

Stimmt 🙂

Nun kommt das Nächste:

MyObj = (_Module)Activator.CreateInstance(MyType);

Diese Zeile wirft die Meldung

Value cannot be null.
Parameter name: type

Nun, das Komische ist nur, dass er die dll einwandfrei einbindet. Denn die "LoadFrom"-Methode wirft keine Fehlermeldung. Nun scheint er die ModuleMySQL-Klasse nicht zu finden.. 🤔

kOOni

E
e86 Themenstarter:in
56 Beiträge seit 2004
vor 18 Jahren

Hallo,

System.Reflection.Assembly AssemblyModuleMySQL;

AssemblyModuleMySQL = System.Reflection.Assembly.LoadFrom(Path +
"\\eQ_ModuleMySQL.dll");

Type MyType = Type.GetType("eQ_ModuleMySQL.ModuleMySQL");

_Module MyObj = new _Module();

MyObj = (_Module)Activator.CreateInstance(MyType);

also irgendwie stehe ich zur Zeit voll auf dem Schlauch, aber nicht, weil ich keine Ahnung hab, sondern weil das Ding nicht laufen will, obwohl alles so ist, wie es sein müsste.

Nutze ich oberen Code, so sagt er mir, dass MyType null ist. Er findet also die Klasse ModuleMySQL nicht, obwohl sie besteht.

Und jetzt kommts: Wenn ich mir per AssemblyModuleMySQL.GetTypes (...) alle gefundenen Types zurückgeben lasse, dann hat er sie aufeinmal drin.. 🤔

Entweder hab ich ein Brett vorm Kopf oder das Programm spinnt... 😜

Danke für eure Hilfe!

kOOni

L
667 Beiträge seit 2004
vor 18 Jahren

Nun kommt das Nächste:

C#-Code:
MyObj = (_Module)Activator.CreateInstance(MyType);

Diese Zeile wirft die Meldung

Zitat:
Value cannot be null.
Parameter name: type

Kann es sein, dass der Konstruktor Deines eQ_ModuleMySQL.ModuleMySQL noch Parameter benötigt ? Die Activator.CreateInstance Methode hat eine Überladung, mit der Du Konstruktor-Parameter übergeben kannst. Eventuell braucht das ModuleMySQL einen Paramter zwingend, und beschwert sich wenn dieser null (=nicht angegeben) ist.

"It is not wise to be wise" - Sun Tzu