Entwickler-Ecke
C# - Die Sprache - Plugin mit RemObjects Hydra oder Interfaces in C#
Delete - Mi 16.01.08 13:44
Titel: Plugin mit RemObjects Hydra oder Interfaces in C#
Crossposting aus der Delphi-Praxis:
http://www.delphipraxis.net/topic126665_plugin+mit+remobjects+hydra+oder+interfaces+in+c.html
Ich arbeite gerade mit dem Plugin-System Hydra von RemObjects. Unter Delphi hab eich es schon geschafft Daten mittels eines Interfaces auszutauschen zwischen Plugin und Host-Anwendung.
Jetzt versuche ich das gleiche unter C#. Leider sind da die Beispiele etwas mager. Mein bisheriger Versuch sieht so aus:
Plugin-Interface:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
| using System; using System.Collections.Generic; using System.Text;
namespace HydraPluginHost { interface PluginInterface { string GetAuthor(); string GetVersion(); } } |
Plugin:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31:
| using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using RemObjects.Hydra; using HydraPluginHost;
namespace HydraPlugin { [Plugin, VisualPlugin] public partial class HydraPlugin : VisualPlugin, PluginInterface { public HydraPlugin() { InitializeComponent(); }
public string GetAuthor() { return "Michael Puff"; }
public string GetVersion() { return "0.1 [Demo]"; } } } |
Laden des Plugin in der Host-anwendung:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
| private void lbPlugins_DoubleClick(object sender, EventArgs e) { try { if (lbPlugins.SelectedIndex == -1) return; if (lbPlugins.SelectedItem is PluginDescriptor) { currentPlugin = null; hostPanel1.UnhostPlugin(); currentPlugin = moduleManager1.CreateInstance(lbPlugins.SelectedItem as PluginDescriptor); hostPanel1.HostPlugin(currentPlugin as IBasePlugin); this.Text = (currentPlugin as PluginInterface).GetAuthor(); }
} catch (Exception ex) { MessageBox.Show(ex.Message); } } |
Compilieren tut er es ohne Fehler. Allerdings zur Laufzeit bekomme ich in der Zeile
this.Text = (currentPlugin as PluginInterface).GetAuthor(); den Fehler:
| Zitat: |
| Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt. |
Ich müsste wohl erst eine Objekt-Instanz von PluginInterface erzeugen. Aber da stehe ich jetzt etwas auf dem Schlauch, wie ich das mache.
Christian S. - Mi 16.01.08 15:03
Hallo!
So scheint es zu gehen:
Interface
1: 2: 3:
| IAuthor = public interface property Author : String read; end; |
Plugin
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
| [Plugin, VisualPlugin] Plugin1 = public class(RemObjects.Hydra.VisualPlugin, IAuthor) {$REGION Plugin Designer generated fields} private components: System.ComponentModel.Container; method InitializeComponent; {$ENDREGION} private protected method Dispose(aDisposing: boolean); override; public property Author : String read 'Christian Stelzmann'; constructor; end; |
Plugin laden
1: 2: 3:
| moduleManager1.LoadModule(someFilename); var thePlugin := IAuthor(moduleManager1.CreateInstance('PluginModule1.Plugin1')); MessageBox.Show(thePlugin.Author); |
Delete - Mi 16.01.08 15:12
Besten Dank, auch wenn ich das noch nicht verstehe.
Hast du das irgendwie mit einem Assistenten gemacht? Einen Plugin Designer kann ich bei mir VS 2008 Beta) nirgends finden.
Christian S. - Mi 16.01.08 15:19
Hallo!
zum Plugin:
Hydra hat in meinem Visual Studio einen neuen Projekttyp angelegt namens "Plugin Module", den ich unter der Kategorie "RemObjects Hydra" finde, wenn ich ein neues Projekt anlegen will. Wenn ich ein solches Projekt anlege und diesem Projek über Rechtsklick -> Add -> New Item eine neue Datei hinzufügen will, kann ich dort aus verschiedenen Hydra-Items wählen, so auch dem Visual-Plugin. Für das gibt's dann auch den normalen WinForms-Designer.
Hostanwendung:
Ich lege eine normale WinForms-Anwendung an, in meiner Toolbox gibt es die Kategorie "RemObjects Hydra". Darin befindet sich der ModuleManager und das HostPanel. Die kann ich dann einfach auf die Form ziehen.
Grüße
Christian
Delete - Mi 16.01.08 15:26
Ja, so geht das mit dem VS auch. Der resultierende Plugin-Code sieht dann so aus:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
| using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using RemObjects.Hydra;
namespace PluginModule1 { [Plugin, VisualPlugin] public partial class Plugin1 : VisualPlugin { public Plugin1() { InitializeComponent(); } } } |
Christian S. - Mi 16.01.08 15:31
Da der eine partial Class anlegt, dürfte der Rest in einer anderen Datei stecken. Bei C# ist es ja üblich, den Designer-Code vom Rest zu trennen.
Delete - Mi 16.01.08 15:38
Ja, es gibt noch eine
ModuleController.cs Datei:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59:
| namespace PluginModule1 { partial class TheModuleController { private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); }
#region Windows Form Designer generated code
private void InitializeComponent() { this.components = new System.ComponentModel.Container(); this.smallImages = new System.Windows.Forms.ImageList(this.components); this.largeImages = new System.Windows.Forms.ImageList(this.components); this.smallImages.ColorDepth = System.Windows.Forms.ColorDepth.Depth8Bit; this.smallImages.ImageSize = new System.Drawing.Size(16, 16); this.smallImages.TransparentColor = System.Drawing.Color.Transparent; this.largeImages.ColorDepth = System.Windows.Forms.ColorDepth.Depth8Bit; this.largeImages.ImageSize = new System.Drawing.Size(16, 16); this.largeImages.TransparentColor = System.Drawing.Color.Transparent; this.LargeImages = this.largeImages; this.SmallImages = this.smallImages;
}
#endregion
private System.Windows.Forms.ImageList smallImages; private System.Windows.Forms.ImageList largeImages; } } |
Ich habe das Laden jetzt mal so probiert:
C#-Quelltext
1: 2: 3: 4: 5:
| currentPlugin = moduleManager1.CreateInstance(lbPlugins.SelectedItem as PluginDescriptor); hostPanel1.HostPlugin(currentPlugin as IBasePlugin); IPluginInterface pluginInterface; pluginInterface = (IPluginInterface)moduleManager1.CreateInstance(lbPlugins.SelectedItem as PluginDescriptor); this.Text = pluginInterface.Caption(); |
Da bekomme ich die Fehlermeldung:
| Zitat: |
| Das Objekt des Typs HydraPlugin.HydraPlugin kann nicht in Typ HydraPluginHost.IPluginInterface umgewandelt werden. |
Christian S. - Mi 16.01.08 15:50
Hm. Das ist komisch. :gruebel:
Wenn Du mal pluginInterface als object deklarierst, in die Zeile nach dem CreateInstance einen Breakpoint setzt und dann schaust, was in pluginInterface drin ist, wie sieht das dann aus?
Delete - Mi 16.01.08 15:54
Dann ist es
null:
C#-Quelltext
1: 2:
| object pluginInterface; pluginInterface = (IPluginInterface)moduleManager1.CreateInstance(lbPlugins.SelectedItem as PluginDescriptor); |
Christian S. - Mi 16.01.08 15:57
Lass mal den Cast weg und schau, was dann zurück gegeben wird. Falls das auch Null ist, versuch mal, statt eines PluginDescriptors nur den Namen des Plugins (Format: Namespace.Klasse) als String zu übergeben.
Delete - Mi 16.01.08 16:10
Also, wenn ich den Cast weglasse:
C#-Quelltext
1: 2:
| object pluginInterface; pluginInterface = moduleManager1.CreateInstance(lbPlugins.SelectedItem as PluginDescriptor); |
Hab eich laut Debugger ein:
| Zitat: |
| pluginInterface {HydraPlugin.HydraPlugin} object {HydraPlugin.HydraPlugin} |
da drin.
Und wenn ich nur den Namen des Plugins übergebe:
| Zitat: |
| cannot convert from 'object' to 'RemObjects.Hydra.PluginDescriptor' |
Wenn wir das morgen noch nicht geschafft haben, schreibe ich mal den Support an. :roll:
Delete - Mo 21.01.08 15:26
Das erste Problem wäre dank Christian gelöst. Man muss das Interface in ein Assembly verpacken und jenes dann referenzieren. Fügt man nur die c-Datei dem Projekt hinzu hat man zwei unterschiedliche Referenzen im Plugin- und Host-Code.
Ich stelle noch ein Demo hier ein. aber vorher muss noch ein anderes Problem gelöst werden und zwar die Kommunikation vom Host zum Plugin.
@Christian: Wenn ich es so mache, wie du es in der PN vorschlägst:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9:
| namespace PluginInterface { public interface IPluginInterface { string Author(); string Caption(); string HostExePath { get; set; } } } |
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| public string HostExePath { get { return hostExePath; } set { hostExePath = value; } } |
Bekomme ich beim Laden der Plugins eine Fehlermeldung:
| Zitat: |
| Mindestens ein Typ in der Assembly kann nicht geladen werden. Rufen Sie die LoaderExceptions-Eigenschaft ab, wenn Sie weitere Informationen benötigen. |
Ich hatte mir das auch eher so gedacht, dass ich in dem Plugin Assembly ein IHostInterface implementiere und dann dann ähnlich löse, wie mit dem IPluginInterface.
Hinzukommt, dass ich es noch irgendwie schaffen muss, dass ein Event ausgelöst wird, wenn das Plugin Daten an den Host übergibt.
Robert_G - Mo 21.01.08 16:23
Ich würde dir hier gerne weiterhelfen, aber du solltest als langjähriger Moderator selbst wissen, dass man ohne Infos keine Antworten geben kann.
Also: Was enthält LoaderExceptions?
Hast du beide Seiten neu kompiliert, nachdem du das Interface geändert hast?
Also mehr als so offensichtliche Dinge kann ich erstmal nicht fragen, für mehr fehlen die nötigen Details...
Delete - Mo 21.01.08 16:26
Robert_G hat folgendes geschrieben: |
Ich würde dir hier gerne weiterhelfen, aber du solltest als langjähriger Moderator selbst wissen, dass man ohne Infos keine Antworten geben kann.
Also: Was enthält LoaderExceptions?
Hast du beide Seiten neu kompiliert, nachdem du das Interface geändert hast?
Also mehr als so offensichtliche Dinge kann ich erstmal nicht fragen, für mehr fehlen die nötigen Details... |
Jupp habe bneide Seiten kompiliert. Was LoadException enthält hab eich noch nicht rausfinden können. Jetzt ist aber auch erst mal Feierabend.
PS: Das Posting war auch als Feedback für Christian gedacht. ;)
Delete - Mi 23.01.08 09:50
Ich versuche jetzt einen anderen Weg zu gehen mit einem extra Interface für den Host, um Informationen vom Host an das Plugin zu übergeben.
Interface Assembly:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| namespace PluginInterface { public interface IPluginInterface { string Author(); string Caption(); }
public interface IHostInterface { string GetHostExepath(); } } |
Host Implementation:
C#-Quelltext
1: 2: 3: 4: 5:
| public string HostExepath() { return Application.ExecutablePath; } |
Aufruf im Plugin:
C#-Quelltext
1: 2: 3: 4: 5: 6:
| private void button1_Click(object sender, EventArgs e) { string s; s = (Host as IHostInterface).HostExepath(); label2.Text = s; } |
Leider bekomme ich beim Betätigen der Schaltfläche die Fehlermeldung:
| Zitat: |
| Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt. |
RemObjects Hydra gibt es auch für Delphi und da geht es genauso, wie ich es auch hier in C# versuche und da funktioniert es eben.
Christian S. - Mi 23.01.08 10:01
Wie genau hast Du das Interface beim Host eingebaut? Ich habe nicht so viel Erfahrung mit Hydra, aber der Host ist ja, denke ich, nicht die Form, sondern das HostPanel oder der ModuleManager.
Luckie hat folgendes geschrieben: |
| RemObjects Hydra gibt es auch für Delphi und da geht es genauso, wie ich es auch hier in C# versuche und da funktioniert es eben. |
Du vergleichst Äpfel mit Birnen ;-)
Delete - Mi 23.01.08 10:42
Wie ich es eingebaut habe? Ist nichts wildes:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:
| namespace HydraHost { public partial class Form1 : Form, IHostInterface { private IHYCrossPlatformPlugin currentPlugin;
public string HostExepath() { return Application.ExecutablePath; }
public Form1() { InitializeComponent(); ...; ...; |
Christian S. - Mi 23.01.08 13:56
Wie ich schon schrieb: Ich bezweifle, dass die Form der Host ist.
Delete - Mi 23.01.08 14:04
In der Host-Anwendung ist es dieses Hydra-Host-Panel, das stimmt schon. Das heißt, ich müsste das Interface im Kontext dieses Panels implementieren? Aber wie komme ich da ran?
Christian S. - Mi 23.01.08 14:20
Andere Idee:
Erweitere das IPluginInterface um Folgendes:
C#-Quelltext
1: 2: 3: 4:
| public interface IPluginInterface { Form1 theHostForm {get; set;} } |
Im Host kannst Du nach dem Laden des Plugins dann einfach den Host setzen:
C#-Quelltext
1:
| thePlugin.theHostForm = this; |
Und - tada! - Du kannst im Plugin jede public-Methode der Form benutzen :)
Delete - Mi 23.01.08 14:57
Das wäre schon mal eine Lösung. Ich habe s jetzt so:
Interface Assembly:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9:
| namespace PluginInterface { public interface IPluginInterface { string Author(); string Caption(); Form theHostForm { get; set; } } } |
Plugin:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49:
| namespace HydraPlugin { [Plugin, VisualPlugin] public partial class HydraPlugin : VisualPlugin, IPluginInterface { private Form hostForm; public HydraPlugin() { InitializeComponent(); }
public string Author() { return "Michael Puff"; }
public string Caption() { return "[Hyra Plugin - Demo]"; }
public Form theHostForm { get { return hostForm; } set { hostForm = value; } }
private void button1_Click(object sender, EventArgs e) { try { string s; s = theHostForm.HostExepath(); label2.Text = s; } catch (Exception ex) { MessageBox.Show(ex.Message); } } } } |
aber dan bekom eich die Fehlermeldung:
| Zitat: |
| 'System.Windows.Forms.Form' does not contain a definition for 'HostExepath' and no extension method 'HostExepath' accepting a first argument of type 'System.Windows.Forms.Form' could be found (are you missing a using directive or an assembly reference?) |
Christian S. - Mi 23.01.08 15:02
Form ist die Stammklasse aller Formen. Du leitest Dir ja in der Hostanwendung eine Klasse davon ab, die musst Du nehmen.
Delete - Mi 23.01.08 15:04
Dann muss ich die Host-Anwendung aber referenzieren, richtig?
Christian S. - Mi 23.01.08 15:06
Hast Du für das Plugin-Interface doch sowieso :nixweiss:
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2026 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!