Laden...

Plugin: Exception beim Abonnieren eines Events

Erstellt von marcom vor 15 Jahren Letzter Beitrag vor 15 Jahren 4.721 Views
M
marcom Themenstarter:in
123 Beiträge seit 2007
vor 15 Jahren
Plugin: Exception beim Abonnieren eines Events

Hallo zusammen,

ich arbeite an einem Plugin System das ungefähr so aussieht: Es existiert eine DLL mit einer Basisklasse für ein Plugin und ein paar Interfaces wie z.B.

public interface IBlub
{
    event System.EventHandler MyEvent;
...
}

Dann hab ich noch mein Hauptprogramm welches eine Referenz zu dieser DLL enthält. Mein Plugin, das ebenfalls als DLL vorliegt, referenziert ebenfalls die Plugin-DLL. Das Plugin besteht aus einer Klasse die von der Basisklasse aus der Plugin-DLL erbt und dabei ein IBlub Objekt bekommt. Soweit so gut. Sobald ich aber im Plugin das Event abonnieren will kommt es zu einer FileNotFound Exception!

So funktioniert mein Plugin System:
Alle Plugins liegen im Unterverzeichnis Plugins. Ich arbeite mit einer AppDomain das ungefähr so initialisiert wird:


AppDomainSetup setup = new AppDomainSetup();

setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
setup.PrivateBinPath = Path.GetDirectoryName(FileName).Replace(AppDomain.CurrentDomain.BaseDirectory, "");
setup.PrivateBinPathProbe = "true";

this.AppDomain = AppDomain.CreateDomain("SandBox", null, setup);
BasePlugin Plugin = this.AppDomain.CreateInstanceAndUnwrap(this.AssemblyName, TypeName) as BasePlugin;

Das funktioniert soweit auch aber im nächsten Schritt, wo ich Plugin das IBlub Objekt übergebe, bekomme ich diese Exception:

System.Reflection.TargetInvocationException: Ein Aufrufziel hat einen Ausnahmefehler verursacht. ---> System.IO.FileNotFoundException: Die Datei oder Assembly TestPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null oder eine Abhängigkeit davon wurde nicht gefunden. Das System kann die angegebene Datei nicht finden.
Dateiname: TestPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
bei System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)
bei System.Reflection.Assembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)
bei System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
bei System.Reflection.Assembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
bei System.Reflection.Assembly.Load(String assemblyString)
bei System.Reflection.MemberInfoSerializationHolder..ctor(SerializationInfo info, StreamingContext context)

Interessanterweise: wenn ich meine DLL in das Bin Verzeichnis packe, funktioniert's. !?!

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo marcom,

bei einer TargetInvocationException interessiert nur die InnerException.

herbivore

M
marcom Themenstarter:in
123 Beiträge seit 2007
vor 15 Jahren

Hi herbivore

Here you go:

System.IO.FileNotFoundException: Die Datei oder Assembly TestPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null oder eine Abhängigkeit davon wurde nicht gefunden. Das System kann die angegebene Datei nicht finden.
Dateiname: TestPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
bei System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)
bei System.Reflection.Assembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)
bei System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
bei System.Reflection.Assembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
bei System.Reflection.Assembly.Load(String assemblyString)
bei System.Reflection.MemberInfoSerializationHolder..ctor(SerializationInfo info, StreamingContext context)

=== Zustandsinformationen vor Bindung ===
LOG: Benutzer = Rechnername\Benutzername
LOG: DisplayName = TestPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
(Fully-specified)
LOG: Appbase = file:///D:/Projects/PluginProjekt/Bin/
LOG: Ursprünglicher PrivatePath = NULL
Aufruf von Assembly : (Unknown).
===
LOG: Diese Bindung startet im default-Load-Kontext.
LOG: Die Anwendungskonfigurationsdatei wird verwendet: D:\Projects\PluginProjekt\Bin\PluginProjekt.exe.Config
LOG: Die Computerkonfigurationsdatei von C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\config\machine.config wird verwendet.
LOG: Die Richtlinie wird derzeit nicht auf den Verweis angewendet (private, benutzerdefinierte, teilweise oder pfadbasierte Assemblybindung)
LOG: Die gleiche Bindung ist bereits aufgetreten und hat den Fehler hr = 0x80070002 verursacht.

Anmerkungen:
PluginTest ist die DLL die dynamisch geladen wurde (Befindet sich unter Bin\Plugins)
PluginProjekt ist das Hauptprogramm.

4.221 Beiträge seit 2005
vor 15 Jahren

Kann es allenfalls sein, dass das Interface-Dll nicht gefunden wird ... mach mal eine Kopie de Interface-Dlls in das Unterverzeichnis (als Test).

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

M
marcom Themenstarter:in
123 Beiträge seit 2007
vor 15 Jahren

Hallo Programmierhans,

Die Interface-DLL liegt sowohl im Plugins Verzeichnis als auch im Bin Verzeichnis.

Gruß

Mark

4.221 Beiträge seit 2005
vor 15 Jahren

Referenzierst du im Plugin noch irgendwelche DLL's welche nicht im GAC liegen ?

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

M
marcom Themenstarter:in
123 Beiträge seit 2007
vor 15 Jahren

Nope, und das gleiche gilt auch für die Interface-DLL.

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo marcom,

dann kann es noch sein, dass die DLL nicht in der richtigen Version vorliegt.

Ansonsten ist ja die Fehlermeldung sehr eindeutig. Alles weitere müsstest du alleine lösen können.

herbivore

M
marcom Themenstarter:in
123 Beiträge seit 2007
vor 15 Jahren

Hallo herbivore,

es ist die gleiche DLL die referenziert wurde die auch im Verzeichnis liegt.
Die Fehlermeldung ist eindeutig aber ich glaube dahinter versteckt sich mehr (wieso funktioniert es plötzlich wenn die TestPlugin.dll im Bin Verzeichnis liegt obwohl ich der AppDomain gesagt habe, dass die DLL im Plugins Verzeichnis liegt.)

Glaub mir, ich poste nicht so schnell wie vielleicht andere. Und schließlich ist das hier doch ein Forum?!

M
marcom Themenstarter:in
123 Beiträge seit 2007
vor 15 Jahren

Ich habe jetzt mal eine mini-Solution erstellt, wo das Problem auftritt. Vielleicht findet sich jemand der kurz Zeit hat und mal reinschauen kann?

Danke!

S
248 Beiträge seit 2008
vor 15 Jahren

Hallo,

füge zu deinem "Host" Projekt eine "Application Configuration File" hinzu mit diesem Inhalt:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="Plugins"/>
    </assemblyBinding>
  </runtime>
</configuration>

Das Problem ist, dass die aufrufende AppDomain die Assembly ebenfalls finden muss. Da sie aber nur im eigenen Ordner sucht, kann sie diese nicht finden.

Spooky

M
marcom Themenstarter:in
123 Beiträge seit 2007
vor 15 Jahren

Hallo Spooky,

vielen Dank! Das war der Trick! 👍

Jetzt habe ich aber das Problem, dass die DLL in die AppDomain vom Host geladen wird und somit nicht mehr entladen werden kann, was mein Plugin System hinfällig macht.
Ich kann zwar die AppDomain vom Plugin entladen, aber die DLL bleibt weiterhin in der Host-AppDomain geladen und kann somit weder gelöscht noch ersetzt werden.

Interessanterweise funktionieren die Methodenraufrufe ohne die App.Config problemlos - erst das Abonnieren des Events verursacht dieses Verhalten.

Hat dafür jemand einen Tipp?

Danke!

Gruß

Mark

161 Beiträge seit 2007
vor 15 Jahren

Unabhängig davon hast du dir schon mal System.AddIn angeschaut? Ich war auch drauf und dran ein eigenes PlugIn Framework zu entwickeln und hab mich noch rechtzeitig nach möglichen Alternativen umgesehen.

"Eine wirklich gute Idee erkennt man daran,
dass ihre Verwirklichung von vorneherein ausgeschlossen erscheint."
(Albert Einstein)

M
marcom Themenstarter:in
123 Beiträge seit 2007
vor 15 Jahren

Hallo kelkes,

das ist sicherlich auch interessant - nur würde ich gerne mein Konzept zum Laufen kriegen. System.AddIn scheint mit dem ganzen drum-rum etwas viel Aufwand zu sein, zumal ich schon (hoffentlich!) "kurz davor" bin 😉

M
marcom Themenstarter:in
123 Beiträge seit 2007
vor 15 Jahren

So ich glaube ich verstehe so langsam was passiert:

die Kommunikation zwischen verschiedenen AppDomains läuft bekanntlich über Remoting. Wenn man bei Remoting mit Events arbeitet, muss Objekt A Objekt B kennen und umgekehrt (die Assembly muss sowohl beim Server als auch beim Client vorliegen).
Und genau das verursacht den Fehler: die Host-AppDomain versucht das Assembly des Plugins zu laden sobald der delegate "rübergeschickt" werden soll, findet dabei die Assembly nicht -> Exception. Und mit Spooks Lösung wird die Assembly geladen, allerdings ist das Plugin dann fest in der AppDomain geladen und ich kann sie nicht mehr entladen.

Meine unschöne Lösung ist, dass ich einfach alle Events durch abstrakte Methoden ersetze (die in einer anderen Klasse implementiert sind) und rufe diese Methoden auf. Das funktioniert auch.

Unterstützt System.AddIn Events AppDomain-übergreifend? Wenn ja, wie funktioniert es dort? Kennt jemand eine bessere Lösung für mein Problem? Fragen über Fragen 8o

Viele Grüße

Mark

161 Beiträge seit 2007
vor 15 Jahren

Ich empfehle System.AddIn zu verwenden.

http://www.codeplex.com/clraddins

Hier findest du einen sogenannten PipelineBuilder der dir beim Aufbau der AddIn-Pipe hilft (Im Endeffekt muss man nur mehr den Contract definieren, die Adapter und Views werden generiert).

Ausserdem findest du auf dieser Seite diverse Beispiele unter anderem eines wie man AppDomain übergreifend mit Events arbeitet.

Am Anfang schaut es nach viel Arbeit aus, aber wenn man sich etwas eingelesen hat ist das ganze eigentlich einfach und logisch aufgebaut und man entwickelt neue PlugIn Strukturen wirklich flott.

"Eine wirklich gute Idee erkennt man daran,
dass ihre Verwirklichung von vorneherein ausgeschlossen erscheint."
(Albert Einstein)